Skip to main content

Wallets

A wallet is a ₦-denominated account tied to a user in your system. You identify your user with a userRef — your own user ID (UUID, database ID, or any string up to 255 characters). PayKore never stores your user's personal details, only the reference you provide.


Creating a wallet

curl -X POST https://api.paykore.dev/v1/wallets \
-H "X-API-Key: sk_test_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"userRef": "user_123",
"currency": "NGN",
"metadata": {"plan": "premium"}
}'

Request fields:

FieldRequiredDescription
userRefYesYour user's ID. Max 255 characters. Must be unique per currency within your partner account.
currencyYesAlways "NGN" for now.
metadataNoAny JSON object. Returned as-is on every wallet response. PayKore does not read it.

Response:

{
"id": "wlt_9f3kA2mXpQ",
"partner_id": "prt_X1y2Z3",
"user_ref": "user_123",
"currency": "NGN",
"status": "active",
"balance": {
"kobo": 0,
"naira": "0.00"
},
"nuban": "0123456789",
"bank_name": "PayKore MFB",
"metadata": {"plan": "premium"},
"created_at": "2025-06-01T10:00:00Z",
"updated_at": "2025-06-01T10:00:00Z",
"meta": {
"request_id": "req_7hJkL9mNpQ"
}
}

TypeScript SDK:

const wallet = await paykore.wallets.create({
userRef: 'user_123',
currency: 'NGN',
metadata: { plan: 'premium' },
});

console.log(wallet.id); // "wlt_9f3kA2mXpQ"
console.log(wallet.nuban); // "0123456789" — the user's bank account number
note

The nuban and bank_name fields give you the user's real Nigerian bank account number. Display these to your user so they can receive external transfers directly into their wallet.


Fetching a wallet and balance

curl https://api.paykore.dev/v1/wallets/wlt_9f3kA2mXpQ \
-H "X-API-Key: sk_test_YOUR_KEY_HERE"

The balance in the response is computed from the ledger in real time and cached for 60 seconds. For the exact balance (bypassing cache), add ?nocache=true:

curl "https://api.paykore.dev/v1/wallets/wlt_9f3kA2mXpQ?nocache=true" \
-H "X-API-Key: sk_test_YOUR_KEY_HERE"
tip

Use ?nocache=true sparingly — for example, immediately after a credit to confirm the balance updated. For display purposes (dashboards, transaction lists), the 60-second cache is fine and reduces load.


Wallet status

StatusCan debitCan creditDescription
pendingNoNoWallet just created; NUBAN being provisioned by MFB (usually under 5 seconds).
activeYesYesNormal operating state.
frozenNoYesOutbound blocked. Can still receive funds. Used for compliance holds.
closedNoNoPermanent. No further operations possible. Create a new wallet if needed.

Check status from the response status field. If a debit fails with WALLET_FROZEN or WALLET_CLOSED, check the wallet's current status before retrying.


Transaction history

curl "https://api.paykore.dev/v1/wallets/wlt_9f3kA2mXpQ/transactions?type=p2p_transfer&from=2025-06-01T00:00:00Z&limit=20" \
-H "X-API-Key: sk_test_YOUR_KEY_HERE"

Query parameters:

ParamDescription
typeFilter by type: p2p_transfer, bank_transfer, ussd_payment, qr_payment, funding
statusFilter by status: pending, processing, completed, failed
fromISO 8601 start date (inclusive)
toISO 8601 end date (inclusive)
limitResults per page (default 20, max 100)
cursorCursor for next page (from previous response pagination.next_cursor)

Response:

{
"data": [
{
"id": "tx_4qYzA1bCdE",
"type": "p2p_transfer",
"status": "completed",
"amount_kobo": 500000,
"amount_naira": "5000.00",
"direction": "debit",
"created_at": "2025-06-01T10:03:00Z"
}
],
"pagination": {
"total": 42,
"limit": 20,
"has_more": true,
"next_cursor": "cur_aB1cD2eF3g"
},
"meta": {
"request_id": "req_5mNoPqRsTu"
}
}

To fetch the next page, pass the cursor:

curl "https://api.paykore.dev/v1/wallets/wlt_9f3kA2mXpQ/transactions?cursor=cur_aB1cD2eF3g" \
-H "X-API-Key: sk_test_YOUR_KEY_HERE"

Common patterns

Create a wallet on user sign-up

The standard pattern is to create a PayKore wallet when a user registers in your system, then store the wallet ID in your database:

// In your user registration handler
async function registerUser(email: string, password: string) {
// 1. Create user in your database
const user = await db.users.create({ email, password });

// 2. Create PayKore wallet
const wallet = await paykore.wallets.create({
userRef: user.id,
currency: 'NGN',
});

// 3. Store wallet ID on the user record
await db.users.update(user.id, { paykoreWalletId: wallet.id });

return user;
}

This gives every user a wallet immediately. You look up paykoreWalletId from your own database whenever you need to debit or credit that user — no need to call GET /v1/wallets just to get the ID.

Handle duplicate userRef

If you call POST /v1/wallets for a userRef that already has a wallet in that currency, PayKore returns 409 DUPLICATE_WALLET:

{
"error": {
"code": "DUPLICATE_WALLET",
"message": "A wallet for userRef 'user_123' in NGN already exists.",
"http_status": 409,
"existing_wallet_id": "wlt_9f3kA2mXpQ"
}
}

The existing_wallet_id field is included so you can fetch or store it without a separate lookup. Handle this gracefully rather than treating it as a fatal error — it commonly occurs if a user signs up twice or if your registration handler retries:

try {
const wallet = await paykore.wallets.create({ userRef: user.id, currency: 'NGN' });
return wallet;
} catch (err) {
if (err.code === 'DUPLICATE_WALLET') {
// Already exists — fetch it
return await paykore.wallets.get(err.existingWalletId);
}
throw err;
}

SDK examples

TypeScript:

import { PayKore } from '@paykore/sdk';

const paykore = new PayKore({ apiKey: process.env.PAYKORE_API_KEY });

// Create
const wallet = await paykore.wallets.create({ userRef: 'user_123', currency: 'NGN' });

// Fetch
const fetched = await paykore.wallets.get('wlt_9f3kA2mXpQ');

// Transactions
const txns = await paykore.wallets.transactions('wlt_9f3kA2mXpQ', {
type: 'p2p_transfer',
limit: 20,
});

React Native:

import { PayKoreClient } from '@paykore/react-native-sdk';

const client = new PayKoreClient({ apiKey: PAYKORE_API_KEY });

const wallet = await client.wallets.create({ userRef: currentUser.id, currency: 'NGN' });

Next steps