SANDBOX ← Back to site
Developer Sandbox

Test your integration with no real money

Use the credentials below to build and test your Paylode integration. Sandbox transactions are completely isolated — no real payments are processed.

These are shared test credentials. Do not use them in production. Your live keys are available in your merchant dashboard after KYC approval. Never commit API keys to source code — use environment variables.
Sandbox Credentials
Secret Key (sk_test)
sk_test_b816ad4a088a9fe9245387ff0b05cd3b18e507ae342425f5
Public Key (pk_test)
pk_test_30ce4d587ef509a13469668ebc7b1f56baaab14fc0b64a31
Webhook Secret
4be9052281099e0ff5457f729b7df1d713e4166aca2c43580e9a47c594370046
Dashboard Login
[email protected]
Password: SandboxTest2026!

API base URL: https://paylodeservices.com/api/v1

Test Card Numbers

Use any future expiry date, any 3-digit CVV, and any cardholder name.

Card Number Result Use case
4084 0841 1111 1111 ✓ Success Payment completes immediately
4084 0841 1111 1112 ✗ Declined Insufficient funds / card declined
4084 0841 1111 1113 ✗ Failed Generic card failure
4084 0800 0000 0409 ● OTP Triggers OTP screen (enter any 6 digits)
How to integrate
1
Install the SDK
Choose your language. All SDKs work identically — swap sk_test for sk_live when going live.
# Node.js
npm install paylode-node

# Python
pip install paylode-python

# PHP
composer require paylode/paylode-php
2
Initialize a payment
Create a transaction and redirect your customer to the authorization URL.
Node.js
Python
PHP
cURL
const Paylode = require('paylode-node');
const client = new Paylode('sk_test_b816ad4a088a9fe9245387ff0b05cd3b18e507ae342425f5');

const txn = await client.transaction.initialize({
  email:        '[email protected]',
  amount:       500000,        // ₦5,000 in kobo
  callback_url: 'https://yoursite.com/payment/verify',
  metadata:     { order_id: 'ORD-001' },
});

// Redirect customer to payment page
res.redirect(txn.data.authorization_url);
from paylode import Paylode

client = Paylode('sk_test_b816ad4a088a9fe9245387ff0b05cd3b18e507ae342425f5')

txn = client.transaction.initialize(
  email='[email protected]',
  amount=500000,              # ₦5,000 in kobo
  callback_url='https://yoursite.com/payment/verify',
  metadata={'order_id': 'ORD-001'},
)

# Redirect customer
redirect(txn['data']['authorization_url'])
require 'vendor/autoload.php';
use Paylode\Paylode;

$client = new Paylode('sk_test_b816ad4a088a9fe9245387ff0b05cd3b18e507ae342425f5');

$txn = $client->transaction->initialize([
  'email'        => '[email protected]',
  'amount'       => 500000,
  'callback_url' => 'https://yoursite.com/payment/verify',
]);

header('Location: ' . $txn['data']['authorization_url']);
curl -X POST https://paylodeservices.com/api/v1/transactions/initialize \
  -H "Authorization: Bearer sk_test_b816ad4a088a9fe9245387ff0b05cd3b18e507ae342425f5" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "amount": 500000,
    "callback_url": "https://yoursite.com/payment/verify",
    "metadata": { "order_id": "ORD-001" }
  }'
3
Verify the payment server-side
When the customer returns to your callback URL, always verify server-side. Never trust the frontend.
Node.js
Python
cURL
// GET /payment/verify?reference=TXN-xxxx
const result = await client.transaction.verify(req.query.reference);

if (result.data.status === 'SUCCESS') {
  const orderId = result.data.metadata.order_id;
  await fulfillOrder(orderId);
  res.redirect('/order/complete');
} else {
  res.redirect('/order/failed');
}
result = client.transaction.verify(reference)

if result['data']['status'] == 'SUCCESS':
    order_id = result['data']['metadata']['order_id']
    fulfill_order(order_id)
    return redirect('/order/complete')
else:
    return redirect('/order/failed')
curl https://paylodeservices.com/api/v1/transactions/verify/TXN-xxxx \
  -H "Authorization: Bearer sk_test_b816ad4a088a9fe9245387ff0b05cd3b18e507ae342425f5"
4
Handle webhooks
Webhooks are the reliable way to track payment status in real time. Always verify the signature.
Node.js (Express)
app.post('/webhook/paylode',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const sig    = req.headers['x-paylode-signature'];
    const secret = process.env.PAYLODE_WEBHOOK_SECRET;

    if (!Paylode.webhooks.verify(req.body, sig, secret)) {
      return res.sendStatus(401);
    }

    const event = JSON.parse(req.body);

    switch (event.event) {
      case 'payment.success':
        fulfillOrder(event.data.metadata.order_id);
        break;
      case 'payment.failed':
        notifyCustomer(event.data.customer.email);
        break;
    }

    res.sendStatus(200);
  }
);
5
Go live
Replace your test key with your live key. That's it — no other code changes needed. Complete KYC onboarding to get your live keys.
Webhook Events
EventWhen it fires
payment.successPayment completed successfully
payment.failedPayment was declined or failed
refund.processedA refund was issued to the customer
chargeback.raisedCustomer raised a chargeback with their bank
Payout Test Scenarios

Payouts use JWT authentication (login first, then use the token). Wallet balance must be funded by a Paylode admin before batches can be submitted.

Step 1 — Get an auth token

curl -X POST https://paylodeservices.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"SandboxTest2026!"}'

# Response → {"token": "eyJ..."}
ScenarioHow to triggerExpected result
Single payout POST /payouts/batches — 1 item processing status returned
Batch payout POST /payouts/batches — multiple items Batch created, wallet debited, all items queued
Scheduled payout Add scheduled_at (future ISO date) scheduled status, not yet processed
Insufficient balance Amount exceeds wallet balance INSUFFICIENT_BALANCE error 400
Invalid account account_number not 10 digits Validation error 400
CSV bulk upload POST /payouts/batches/upload (multipart) Batch created from CSV file
Single Payout
Batch Payout
Scheduled
CSV Format
curl -X POST https://paylodeservices.com/api/v1/payouts/batches \
  -H "Authorization: Bearer <YOUR_JWT_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Vendor payment - June",
    "items": [
      {
        "account_number": "0123456789",
        "bank_code": "058",
        "amount": 500000,
        "account_name": "Ada Okonkwo",
        "narration": "Payment for services"
      }
    ]
  }'

# Response
{
  "status": true,
  "data": {
    "batch_id": "...",
    "batch_ref": "PAY-XXXXXX",
    "total_amount": "5000.00",
    "total_items": 1,
    "status": "processing",
    "wallet_balance_after": "45000.00"
  }
}
curl -X POST https://paylodeservices.com/api/v1/payouts/batches \
  -H "Authorization: Bearer <YOUR_JWT_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Staff salaries - June 2026",
    "items": [
      {
        "account_number": "0123456789",
        "bank_code": "058",
        "amount": 20000000,
        "account_name": "Emeka Obi",
        "narration": "June salary"
      },
      {
        "account_number": "9876543210",
        "bank_code": "057",
        "amount": 18000000,
        "account_name": "Fatima Musa",
        "narration": "June salary"
      },
      {
        "account_number": "5556667771",
        "bank_code": "044",
        "amount": 15000000,
        "account_name": "Chidi Nwosu",
        "narration": "June salary"
      }
    ]
  }'
curl -X POST https://paylodeservices.com/api/v1/payouts/batches \
  -H "Authorization: Bearer <YOUR_JWT_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "End of month settlement",
    "scheduled_at": "2026-06-30T09:00:00.000Z",
    "items": [
      {
        "account_number": "0123456789",
        "bank_code": "011",
        "amount": 5000000,
        "narration": "June settlement"
      }
    ]
  }'

# Returns status: "scheduled" (not processed yet)
# CSV format (account_number, bank_code, amount_naira, narration, account_name)
0123456789,058,5000,June salary,Emeka Obi
9876543210,057,4500,June salary,Fatima Musa
5556667771,044,3800,Vendor payment,Chidi Nwosu

# Upload via multipart form
curl -X POST https://paylodeservices.com/api/v1/payouts/batches/upload \
  -H "Authorization: Bearer <YOUR_JWT_TOKEN>" \
  -F "[email protected]" \
  -F "description=June payroll"

Select a bank to populate the examples

Bank codeBank nameType
Loading...

Payout webhook events

Listen for these events on your webhook endpoint:

payout.completed  — item successfully sent to beneficiary
payout.failed  — item failed (wrong account, bank error)
payout.batch.completed  — all items in a batch processed

Test now, verify later

Your test keys (sk_test_… / pk_test_…) work immediately — you can integrate and test every product below before your merchant KYC is approved. Live keys (sk_live_…) only start working once KYC is verified. Sandbox transactions never move real money.

Bank Transfer (Virtual Accounts)

Initialize with the bank_transfer channel. Paylode returns a one-time virtual account; the customer transfers to it and the payment auto-confirms. In sandbox the account and the inbound transfer are simulated — no real funds move.

Initialize a bank-transfer charge

curl -X POST https://paylodeservices.com/api/v1/transactions/initialize \
  -H "Authorization: Bearer sk_test_b816ad4a088a9fe9245387ff0b05cd3b18e507ae342425f5" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "amount": 500000,
    "currency": "NGN",
    "channels": ["bank_transfer"]
  }'
# Response → data.authorization_url (hosted page shows the virtual account number + amount)
# data.product → "VIRTUAL_ACCOUNT"
ScenarioHow to triggerExpected result
Successful transferOpen authorization_url → "I've sent the money"success + payment.success webhook
Virtual account expiryWait past the on-screen timerSession expires, no charge
VerifyGET /transactions/verify/<reference>Returns final status — always verify server-side
USSD

Initialize with the ussd channel. Paylode returns a USSD string the customer dials on their phone to authorise. Sandbox simulates the dial/approve step.

curl -X POST https://paylodeservices.com/api/v1/transactions/initialize \
  -H "Authorization: Bearer sk_test_b816ad4a088a9fe9245387ff0b05cd3b18e507ae342425f5" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "amount": 500000,
    "currency": "NGN",
    "channels": ["ussd"]
  }'
# Response → data.authorization_url (hosted page shows the *USSD code to dial)
# data.product → "USSD"
ScenarioHow to triggerExpected result
ApprovedOpen authorization_url → "Simulate approval"success + payment.success
Timed outDo not approve within the windowfailed — session expired
International Cards (USD)

Pass currency:"USD" with the card channel. Amount is in cents. The transaction is routed as an international card (CARD_INTL), billed and settled entirely in USD. Optionally pass card_scheme or card_bin so the right scheme rate applies.

curl -X POST https://paylodeservices.com/api/v1/transactions/initialize \
  -H "Authorization: Bearer sk_test_b816ad4a088a9fe9245387ff0b05cd3b18e507ae342425f5" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "amount": 50000,
    "currency": "USD",
    "channels": ["card"],
    "card_scheme": "VISA"
  }'
# amount 50000 = $500.00 (cents) · data.product → "CARD_INTL" (or CARD_INTL_VISA)
# data.fee_preview.display → "$17.50" · settled in a separate USD batch
FieldNotes
amountIn cents ($1 = 100). NGN amounts are in kobo.
card_schemeOptional: VISA | MASTERCARD | AMEX | DINERS (rate applied if admin configured one, else flat CARD_INTL).
card_binOptional: first 6 digits — scheme auto-detected if card_scheme omitted.
Test cardUse the test cards above; charge succeeds/declines the same way, reported in USD.
Settlements

Successful charges are grouped into settlement batches (NGN and USD settled separately). Authenticate with a JWT (login first), then fetch your settlements. Sandbox simulates a T+1 cycle so you can see how payouts to your bank account are reported.

# 1) Get a token
curl -X POST https://paylodeservices.com/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"SandboxTest2026!"}'

# 2) List settlement batches
curl https://paylodeservices.com/api/v1/settlements \
  -H "Authorization: Bearer <YOUR_JWT_TOKEN>"

# 3) Your statement (per-currency summary)
curl https://paylodeservices.com/api/v1/reports/merchant-statement \
  -H "Authorization: Bearer <YOUR_JWT_TOKEN>"

Fees are deducted before settlement. NGN and USD are always reported side by side and never summed. The statement shows summary_by_currency so you can reconcile each balance separately.