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 "Authorization: Bearer <sk_test_YOUR_KEY_HERE>" \
-H "Content-Type: application/json" \
-d '{
"user_ref": "user_357",
"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:

{
"data":
{
"ID":"8f47af62-b9eb-4387-827f-ca3903b64450",
"PartnerID":"fec4e29a-dc37-4857-9383-01924a61070f",
"UserRef":"user_357",
"Currency":"NGN",
"Status":"pending",
"AccountNumber":"",
"AccountName":"",
"BankCode":"",
"BankName":"",
"MFBReference":"",
"AccountStatus":"provisioning",
"Metadata":
{
"plan":"premium"
},
"CreatedAt":"2026-08-20T15:20:37.872534+01:00",
"UpdatedAt":"2026-08-20T15:20:37.872534+01:00"
}
}

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/8f47af62-b9eb-4387-827f-ca3903b64450 \
-H "Authorization: Bearer <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/8f47af62-b9eb-4387-827f-ca3903b64450?nocache=true" \
-H "Authorization: Bearer <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/8f47af62-b9eb-4387-827f-ca3903b64450/transactions?type=p2p_transfer&from=2026-08-15T00:00:00Z&limit=20" \
-H "Authorization: Bearer <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":"1061b0fd-6b2f-4232-9600-35fc3597ed6e",
"partner_id":"fec4e29a-dc37-4857-9383-01924a61070f",
"reference":"test-transfer-003",
"type":"p2p_transfer",
"status":"completed",
"amount":500000,
"currency":"NGN",
"source_wallet":"d516e617-df5a-4bbc-800c-c4ff19ad047c",
"dest_wallet":"25a793b6-ca5a-49d0-b74a-20731426e0eb",
"source_account":null,
"dest_account":null,
"fee_breakdown":
{
"customerFee":2500,
"merchantFee":0,
"platformFee":2500,
"mfbCost":0,
"netAmount":497500
},
"split_config":null,
"mfb_reference":"",
"psp_reference":"",
"psp_meta":null,
"idempotency_key":"test-transfer-003",
"description":"",
"metadata":{},
"failed_reason":"",
"completed_at":"2026-08-15T07:33:54.66336+01:00",
"created_at":"2026-08-15T07:33:46.475303+01:00",
"updated_at":"2026-08-15T07:33:54.66336+01:00"
},
{
"id":"93d10a16-17da-48ab-ab09-8ca70331946c",
"partner_id":"fec4e29a-dc37-4857-9383-01924a61070f",
"reference":"test-transfer-002",
"type":"p2p_transfer",
"status":"completed",
"amount":500000,
"currency":"NGN",
"source_wallet":"d516e617-df5a-4bbc-800c-c4ff19ad047c",
"dest_wallet":"25a793b6-ca5a-49d0-b74a-20731426e0eb",
"source_account":null,
"dest_account":null,
"fee_breakdown":
{
"customerFee":2500,
"merchantFee":0,
"platformFee":2500,
"mfbCost":0,
"netAmount":497500
},
"split_config":null,
"mfb_reference":"",
"psp_reference":"",
"psp_meta":null,
"idempotency_key":"test-transfer-002",
"description":"",
"metadata":{},
"failed_reason":"",
"completed_at":"2026-08-14T19:05:30.799963+01:00",
"created_at":"2026-08-14T19:05:16.9709+01:00",
"updated_at":"2026-08-14T19:05:30.799963+01:00"
},
{
"id":"26244797-4a8f-4b12-9ca3-fa2fc6fb9218",
"partner_id":"fec4e29a-dc37-4857-9383-01924a61070f",
"reference":"smoke_p2p_1785701007",
"type":"p2p_transfer",
"status":"completed",
"amount":200000,
"currency":"NGN",
"source_wallet":"d516e617-df5a-4bbc-800c-c4ff19ad047c",
"dest_wallet":"c6935d70-f090-488a-9f49-147607b2d4f7",
"source_account":null,
"dest_account":null,
"fee_breakdown":
{
"customerFee":1000,
"merchantFee":0,
"platformFee":1000,
"mfbCost":0,
"netAmount":199000
},
"split_config":null,
"mfb_reference":"",
"psp_reference":"",
"psp_meta":null,
"idempotency_key":"smoke_p2p_1785701007",
"description":"",
"metadata":{},
"failed_reason":"",
"completed_at":"2026-08-02T21:05:06.330157+01:00",
"created_at":"2026-08-02T21:04:59.530532+01:00",
"updated_at":"2026-08-02T21:05:06.330157+01:00"
}
]
}

To fetch the next page, pass the cursor:

curl "https://api.paykore.dev/v1/wallets/8f47af62-b9eb-4387-827f-ca3903b64450/transactions?cursor=cur_aB1cD2eF3g" \
-H "Authorization: Bearer <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":"WALLET_EXISTS",
"message":"wallet already exists for this user_ref"
}
}

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,
});

Next steps