Sandbox Environment
Overview
Sandbox is a full replica of the PayKore production environment, with one key difference: no real money moves. Every endpoint, every webhook, every error path behaves exactly as it would in live — wallets get real-looking NUBANs, transfers produce real ledger entries, fees are calculated the same way. Use sandbox to build your entire integration, exercise edge cases, and run automated test suites before you ever touch a live key.
Getting sandbox credentials
- Log in to app.paykore.dev
- Go to Settings → API Keys
- Click Create Key, select environment Sandbox
- Copy the key — it starts with
sk_test_
Sandbox and live data are completely isolated. A wallet created in sandbox does not exist in live, and vice versa. You cannot point a sk_test_ key at live data or a sk_live_ key at sandbox data — see Authentication for the full isolation rules.
Sandbox behaviour differences
| Feature | Sandbox | Live |
|---|---|---|
| P2P transfers | Instant, no real money | Instant, real money |
| Bank transfers | Completes in 1–2 seconds | 10s – 24h depending on bank and timing |
| BVN verification | Any 11-digit number → verified (except reserved test values) | Real NIBSS/provider lookup |
| NIN verification | Any 11-digit number → verified | Real provider lookup |
| QR payment intents | Normal flow, test wallets | Normal flow, real wallets |
| USSD payments | Use simulation endpoint (no real telco) | Real telco interaction |
| Settlement | No actual bank payout | Real bank transfer |
| Rate limits | Same as live (300 req/min on Starter) | Same |
Rate limits are intentionally identical between sandbox and live. This means load and stress testing in sandbox gives you an accurate picture of what you'll experience in production.
Test ID formats
Sandbox-issued IDs use the same prefixes as live (wlt_, tx_, kyc_, etc.) — there's no separate prefix to distinguish them visually. The only reliable way to confirm you're looking at sandbox data is the API key used to fetch it, or the environment field if you're inspecting dashboard logs.
If you're debugging a support ticket or log line and unsure which environment an ID belongs to, check which key made the request — sandbox and live IDs are not distinguishable by format alone.
Simulating async events
Some behaviours that happen automatically over real banking rails or telco networks in live must be triggered manually in sandbox, since there's no real infrastructure on the other end.
Simulate USSD confirmation or cancellation
curl -X POST https://api.paykore.dev/v1/sandbox/ussd/simulate \
-H "X-API-Key: sk_test_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{"session_id": "ussd_7gHjK2lMnO", "action": "confirm"}'
{
"session_id": "ussd_7gHjK2lMnO",
"action": "confirm",
"result": "transaction.completed webhook fired",
"transaction_id": "tx_3kLmN4oPqR"
}
Use "action": "cancel" to simulate the user backing out of the USSD flow instead.
Force a bank transfer to complete
curl -X POST https://api.paykore.dev/v1/sandbox/bank-transfer/complete \
-H "X-API-Key: sk_test_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{"transaction_id": "tx_8wXyZ1aBcD"}'
{
"transaction_id": "tx_8wXyZ1aBcD",
"status": "completed",
"result": "transaction.completed webhook fired"
}
By default, sandbox bank transfers already auto-complete within 1–2 seconds. This endpoint is useful when you want to control timing precisely in an automated test, rather than waiting on the default delay.
Force a bank transfer to fail
curl -X POST https://api.paykore.dev/v1/sandbox/bank-transfer/fail \
-H "X-API-Key: sk_test_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{"transaction_id": "tx_8wXyZ1aBcD", "reason": "Beneficiary bank rejected: account dormant"}'
{
"transaction_id": "tx_8wXyZ1aBcD",
"status": "failed",
"result": "transaction.failed webhook fired, source wallet reversed"
}
Use this to test your transaction.failed webhook handler and confirm your code correctly reads the reversal_status field rather than issuing a manual refund.
Resetting sandbox state
curl -X POST https://api.paykore.dev/v1/sandbox/reset \
-H "X-API-Key: sk_test_YOUR_KEY_HERE"
{
"result": "All wallets, transactions, and KYC records for this partner have been deleted.",
"reset_at": "2025-06-01T16:00:00Z"
}
This permanently deletes every wallet, transaction, and KYC record under your sandbox partner account. There is no undo. Use it at the start of an automated test run to guarantee a clean slate — never run it against a key you're not certain is sandbox.
Automated testing example
A complete Jest test demonstrating the standard pattern: reset, create wallets, transfer, verify.
import PayKore from '@paykore/sdk';
import { randomUUID } from 'crypto';
const paykore = new PayKore({
apiKey: process.env.PAYKORE_SANDBOX_KEY!,
environment: 'sandbox',
});
describe('P2P transfer', () => {
let walletA: string;
let walletB: string;
beforeAll(async () => {
// Start from a clean slate
await paykore.sandbox.reset();
const a = await paykore.wallets.create({ userRef: 'test_user_a', currency: 'NGN' });
const b = await paykore.wallets.create({ userRef: 'test_user_b', currency: 'NGN' });
walletA = a.id;
walletB = b.id;
// Fund wallet A so there's something to transfer
await paykore.sandbox.fundWallet(walletA, { amountKobo: 1_000_000 });
});
it('moves funds and updates both balances correctly', async () => {
const transfer = await paykore.transfers.p2p({
sourceWalletId: walletA,
destWalletId: walletB,
amountKobo: 500_000,
reference: `test_${randomUUID()}`,
idempotencyKey: randomUUID(),
});
expect(transfer.status).toBe('completed');
expect(transfer.feeBreakdown.customerFeeKobo).toBe(2_500);
const updatedA = await paykore.wallets.get(walletA);
const updatedB = await paykore.wallets.get(walletB);
expect(updatedA.balanceKobo).toBe(497_500); // 1,000,000 - 500,000 - 2,500 fee
expect(updatedB.balanceKobo).toBe(500_000);
});
it('records the transfer in both wallets transaction history', async () => {
const history = await paykore.wallets.listTransactions(walletA, { type: 'p2p_transfer' });
expect(history.data.length).toBeGreaterThan(0);
expect(history.data[0].destWallet.id).toBe(walletB);
});
});
This test resets sandbox state once before the suite, then exercises the full create → fund → transfer → verify cycle, checking both the immediate response and the resulting wallet balances. Use this as a starting template for your own integration test suite.
Next steps
- Test Scenarios → — The full pre-launch checklist covering every flow.
- Webhooks Overview → — Testing webhook delivery locally with ngrok.