Katpay API Documentation

Build secure payment solutions with Katpay's comprehensive API. Process payments, manage virtual accounts, and handle payouts with ease.

Overview

Katpay provides a robust API for processing payments in Nigeria. Our API allows you to create virtual accounts, process transactions, and manage payouts securely.

Base URL

https://api.katpay.co/v1

Authentication

All API requests require authentication using your API keys. Keep your keys secure and never expose them in client-side code.

Required Headers

Header Description Required
Authorization Bearer token with your API secret Required
api-key Your public API key Required
Content-Type Must be application/json Required
JavaScript
const headers = {
  'Authorization': `Bearer ${apiSecret}`,
  'Content-Type': 'application/json',
  'api-key': apiKey
};

Virtual Accounts

Create static virtual accounts for your customers. These accounts are tied to your merchant account and allow you to receive payments.

POST
/virtual-accounts
Create a new virtual account for a customer

Request Body

Parameter Type Description Required
email string Customer's email address Required
name string Customer's full name Required
phoneNumber string Customer's phone number Required
bankCode array Array of bank codes (e.g., ["PALMPAY", "OPAY"]) Required
merchantID string Your merchant ID Required
JavaScript
const axios = require('axios');

const apiKey = 'your-public-key';
const apiSecret = 'your-api-secret';
const merchantID = 'your-merchant-id';
const headers = {
  'Authorization': `Bearer ${apiSecret}`,
  'Content-Type': 'application/json',
  'api-key': apiKey
};

const data = {
  email: 'customer@example.com',
  name: 'Sadeeq Doe',
  phoneNumber: '+2348012345678',
  bankCode: ['PALMPAY'],
  merchantID:
  
  
   merchantID
};

axios.post('https://api.katpay.co/v1/virtual-accounts', data, { headers })
  .then(response => {
    console.log('Virtual account created:', response.data);
  })
  .catch(error => {
    console.error('Error:', error.response.data);
  });

Supported Banks

Bank Code Bank Name Status
PALMPAY PalmPay Active
OPAY OPay Active
20897 Other Bank Active

Payouts

Programmatically send money to bank accounts in Nigeria. This feature must be enabled in your Merchant Settings.

Create Payout

POST
https://api.katpay.co/v1/payouts

Request Body

Parameter Type Description Required
amount number Amount to transfer in Naira (min 100) Required
bank_code string Destination bank code Required
account_number string 10-digit NUBAN account number Required
account_name string Name of the account holder Required
description string Narration for the transaction Optional
reference string Unique transaction reference Optional

Example Request

JavaScript
const payout = await fetch('https://api.katpay.co/v1/payouts', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_SECRET_KEY',
    'Content-Type': 'application/json',
    'api-key': 'YOUR_PUBLIC_KEY'
  },
  body: JSON.stringify({
    amount: 5000,
    bank_code: "058",
    account_number: "0123456789",
    account_name: "Sadeeq Doe",
    description: "Salary Payment",
    reference: "PAY_123456789"
  })
});

const data = await payout.json();
console.log(data);

Pay with Transfer

Accept one-time payments from customers via direct bank transfer. Each payment request generates a unique payment order — no shared accounts, no ambiguity. Your system initiates a payment, and gets notified automatically when the customer completes the transfer.

How It Works

  1. Your server calls POST /v1/transfer-payments with amount, customer info, and callback URL
  2. KatPay creates a unique payment order and returns bank account details and a checkout_url
  3. Redirect customer to the checkout_url (KatPay hosted checkout — timer, copy button, auto-detect)
  4. Alternatively, build your own UI using the payment_account details from the response
  5. Customer makes a bank transfer to the provided account
  6. KatPay confirms the payment and sends a signed callback to your callback_url and your dashboard webhook URL
  7. Your server verifies the signature and completes the order
💡 Easiest Integration: Just redirect your customer to the checkout_url returned in the response. KatPay handles the entire payment UI, countdown timer, and status updates — no frontend code needed.
POST
/v1/transfer-payments
Initiate a new pay-with-transfer payment. Creates a unique payment order and returns a bank account for the customer.

Headers

Header Value Description
Authorization Bearer {secret_key} Your live or test secret key
Content-Type application/json Required for all requests

Request Body

Parameter Type Description Required
amount number Amount in Naira (min: 100, max: 10,000,000) Required
customer_name string Customer's full name Required
customer_email string Customer's email address Required
callback_url string URL to receive payment confirmation (must be HTTPS in production) Required
merchant_reference string Your unique reference for this payment (e.g., order ID) Required
customer_phone string Customer's phone number Optional
description string Payment description (max 500 chars) Optional
success_url string URL to redirect customer after successful payment Optional
metadata object Custom key-value data returned in the callback Optional
expires_in integer Minutes until payment expires (default: 30, max: 1440) Optional
currency string Currency code (default: NGN) Optional

Success Response (201)

JSON Response
{
  "success": true,
  "message": "Transfer payment initiated successfully",
  "data": {
    "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "merchant_reference": "ORDER_001",
    "internal_reference": "TRF_ABCDEFGHIJ1234567890_1711000000",
    "status": "pending",
    "amount": 5000.00,
    "fee_amount": 125.00,
    "net_amount": 4875.00,
    "currency": "NGN",
    "payment_account": {
      "account_number": "8012345678",
      "account_name": "KatPay John",
      "bank_name": "PalmPay"
    },
    "checkout_url": "https://checkout.katpay.co/pay/transfer/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "customer": {
      "name": "John Doe",
      "email": "john@example.com"
    },
    "expires_at": "2026-03-21T10:30:00+00:00",
    "created_at": "2026-03-21T10:00:00+00:00"
  }
}
JavaScript
const axios = require('axios');

const SECRET_KEY = 'sk_live_your_secret_key';

const response = await axios.post(
  'https://api.katpay.co/v1/transfer-payments',
  {
    amount: 5000,
    customer_name: 'John Doe',
    customer_email: 'john@example.com',
    customer_phone: '+2348012345678',
    callback_url: 'https://yoursite.com/payment/callback',
    merchant_reference: 'ORDER_001',
    description: 'Payment for Order #001',
    metadata: {
      order_id: '001',
      product: 'Premium Plan'
    },
    expires_in: 30
  },
  {
    headers: {
      'Authorization': `Bearer ${SECRET_KEY}`,
      'Content-Type': 'application/json'
    }
  }
);

console.log('Payment account:', response.data.data.payment_account);
// Show these details to your customer:
// Account Number: 8012345678
// Bank: PalmPay
// Amount: NGN 5,000

Hosted Checkout (Recommended)

The easiest way to accept bank transfer payments — just redirect your customer to the checkout_url returned in the initiation response. KatPay provides a fully hosted payment page with:

  • Bank account details with one-click copy
  • Real-time countdown timer with expiry warning
  • Automatic payment detection — actively queries PalmPay every ~12s and falls back to DB polling every 4s
  • “I have made this transfer” button for instant verification on demand
  • Auto-redirect to your success_url when payment completes
  • Fully responsive — mobile-optimized UI your customers can trust
Redirect to Checkout
// After calling POST /v1/transfer-payments
const result = await response.json();

// Redirect customer to KatPay's hosted checkout (recommended)
window.location.href = result.data.checkout_url;

// Or build your own UI using result.data.payment_account

Checkout URL format: https://checkout.katpay.co/pay/transfer/{uuid}

GET
/v1/transfer-payments/{uuid}
Check the status of a transfer payment. Use this to poll for payment completion.

Path Parameters

Parameter Type Description
uuid string The UUID returned when initiating the transfer payment

Payment Status Values

Status Description
pending Waiting for customer to make the transfer
processing Transfer detected, payment is being confirmed
completed Payment confirmed — merchant wallet credited
expired Payment window timed out with no transfer
cancelled Cancelled by merchant via API
failed Payment processing encountered an error
GET
/v1/transfer-payments/verify/{merchant_reference}
Verify a transfer payment using your own merchant reference. This is the recommended way to confirm payment status from your server — similar to Paystack's GET /transaction/verify/:reference.

Path Parameters

Parameter Type Description
merchant_reference string Your unique reference passed when initiating the transfer payment
Best Practice: Always verify payment status from your server using this endpoint after receiving a callback — never trust the callback alone.

Response includes

Field Description
amount The amount you requested (expected amount)
amount_received The actual amount the customer transferred (may differ from requested)
status Current payment status (pending, completed, etc.)
POST
/v1/transfer-payments/{uuid}/cancel
Cancel a pending transfer payment. Only works for payments with status pending.
GET
/v1/transfer-payments
List all your transfer payments. Supports filtering by status and merchant_reference.

Query Parameters

Parameter Type Description
status string Filter by status (pending, completed, expired, cancelled)
merchant_reference string Filter by your merchant reference
per_page integer Results per page (default: 20, max: 100)

Callback Notification

When a customer completes the transfer, KatPay sends a POST request to your callback_url with the payment details. The callback is signed so you can verify it came from KatPay.

Dual Notification: KatPay sends the transfer payment event to both your per-request callback_url and your dashboard-configured Webhook URL (if set). This ensures your server is always notified even if the callback_url is inaccessible.

Callback Headers

Header Description
X-KatPay-Signature HMAC-SHA256 signature for verification
X-KatPay-Timestamp Unix timestamp when callback was sent
X-KatPay-Event Always transfer_payment.completed
X-KatPay-Delivery-Id Unique ID for this delivery (for idempotency)

Callback Body

JSON Callback Payload
{
  "event": "transfer_payment.completed",
  "data": {
    "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "merchant_reference": "ORDER_001",
    "internal_reference": "TRF_ABCDEFGHIJ1234567890_1711000000",
    "status": "completed",
    "amount": 5000.00,
    "amount_received": 5000.00,
    "fee_amount": 125.00,
    "net_amount": 4875.00,
    "currency": "NGN",
    "customer_name": "John Doe",
    "customer_email": "john@example.com",
    "description": "Payment for Order #001",
    "metadata": {
      "order_id": "001",
      "product": "Premium Plan"
    },
    "paid_at": "2026-03-21T10:05:00+00:00",
    "completed_at": "2026-03-21T10:05:01+00:00",
    "payer_account_name": "JOHN DOE",
    "payer_bank_name": "GTBank"
  }
}

Verifying the Callback Signature

Always verify the X-KatPay-Signature header to ensure the callback is authentic. The signature is computed as: HMAC-SHA256(timestamp + "." + payload, your_secret_key)

PHP
<?php
// Your callback endpoint (e.g., https://yoursite.com/payment/callback)

$SECRET_KEY = 'sk_live_your_secret_key'; // Your KatPay Secret Key

$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_KATPAY_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_KATPAY_TIMESTAMP'] ?? '';

// 1. Verify the signature
$expectedSignature = hash_hmac('sha256', $timestamp . '.' . $payload, $SECRET_KEY);

if (!hash_equals($expectedSignature, $signature)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

// 2. Prevent replay attacks (reject if older than 5 minutes)
if (abs(time() - (int)$timestamp) > 300) {
    http_response_code(401);
    echo json_encode(['error' => 'Timestamp too old']);
    exit;
}

// 3. Parse the payload
$data = json_decode($payload, true);
$event = $data['event'];
$payment = $data['data'];

if ($event === 'transfer_payment.completed') {
    $merchantReference = $payment['merchant_reference'];
    $amount = $payment['amount'];
    $status = $payment['status'];
    $metadata = $payment['metadata'];

    // Update your order in your database
    // e.g., Order::where('reference', $merchantReference)->update(['status' => 'paid']);

    // Return 200 to acknowledge receipt
    http_response_code(200);
    echo json_encode(['received' => true]);
} else {
    http_response_code(200);
    echo json_encode(['received' => true, 'note' => 'Unhandled event']);
}
Important Notes
  • Always verify the signature before processing the callback
  • Return HTTP 200 to acknowledge receipt. KatPay retries up to 3 times on non-2xx responses
  • Use merchant_reference to match callbacks to your orders
  • Check the status field — only process completed payments
  • Use X-KatPay-Delivery-Id for idempotency to avoid processing duplicates
  • The metadata object contains whatever you passed during initiation

Supported Bank Codes

When making a payout, you need to pass the correct bank_code for the destination bank. Below is the full list of supported banks and their codes, fetched live from our payment processor.

GET
/api/bank-list
Returns the full list of supported bank codes and bank names for payouts.

Response Fields

Field Type Description
bankCode string Unique code for the bank (use this in payouts)
bankName string Full name of the bank

Bank Code Directory

Search the table below to find the correct bank code for your payout destination.

Loading bank list...

Webhooks

Webhooks allow you to receive real-time notifications when events occur in your Katpay account. Set up webhook endpoints to automatically process payments, track transactions, and handle account updates.

Webhook Events

Event Type Description
virtual_account.payment_received Triggered when a payment is received into a virtual account
transaction.completed Triggered when a transaction is successfully completed
payout.processed Triggered when a payout has been processed
transfer_payment.completed Triggered when a pay-with-transfer payment is confirmed (sent to your callback_url)

Required Headers

Header Description
X-Katpay-Signature HMAC SHA256 signature for webhook verification
X-Katpay-Timestamp Unix timestamp when the webhook was sent

Webhook Handler Implementation

Implement webhook signature verification to ensure webhooks are authentic and from Katpay.

PHP
<?php
/**
 * KatPay Webhook Handler
 * Replace YOUR_SECRET_KEY with your Live Secret Key from KatPay
 */

// Your Live Secret Key from KatPay dashboard
$SECRET_KEY = 'your-webhook-secret-key';

// Get webhook data
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_KATPAY_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_KATPAY_TIMESTAMP'] ?? '';

header('Content-Type: application/json');

try {
    // Validate secret key is set
    if (empty($SECRET_KEY) || $SECRET_KEY === 'your-webhook-secret-key') {
        http_response_code(500);
        echo json_encode(['error' => 'Webhook secret not configured']);
        exit;
    }

    // Validate required headers
    if (empty($signature) || empty($timestamp)) {
        http_response_code(400);
        echo json_encode(['error' => 'Missing required headers']);
        exit;
    }

    // Verify signature
    $signedPayload = $timestamp . '.' . $payload;
    $expectedSignature = hash_hmac('sha256', $signedPayload, $SECRET_KEY);

    if (!hash_equals($expectedSignature, $signature)) {
        http_response_code(401);
        echo json_encode(['error' => 'Invalid signature']);
        exit;
    }

    // Process webhook
    $data = json_decode($payload, true);
    
    if (json_last_error() !== JSON_ERROR_NONE) {
        http_response_code(400);
        echo json_encode(['error' => 'Invalid JSON payload']);
        exit;
    }

    $eventType = $data['event_type'] ?? '';

    // Handle payment received event
    if ($eventType === 'virtual_account.payment_received') {
        $transaction = $data['data']['transaction'] ?? [];
        $customer = $data['data']['customer'] ?? [];
        
        $amount = $transaction['order_amount'] ?? 0;
        $orderNo = $transaction['order_no'] ?? '';
        $transactionId = $transaction['id'] ?? '';
        $customerEmail = $customer['email'] ?? '';
        $customerPhone = $customer['phone'] ?? '';
        
        // Your business logic here
        // Example: Update database, send confirmation email, etc.
        
        // Respond with success
        http_response_code(200);
        echo json_encode([
            'status' => 'success',
            'message' => 'Payment processed'
        ]);
    } else {
        // Handle other event types
        http_response_code(200);
        echo json_encode([
            'status' => 'success',
            'message' => 'Event received'
        ]);
    }

} catch (Exception $e) {
    http_response_code(500);
    echo json_encode(['error' => 'Internal server error']);
}
?>

Example Payload

JSON
{
  "event_type": "virtual_account.payment_received",
  "event_id": "evt_1678901234_a1b2c3d4",
  "timestamp": 1678901234,
  "data": {
    "virtual_account": {
      "account_number": "1234567890",
      "account_name": "Sadeeq Doe",
      "bank_name": "PalmPay",
      "provider_reference": "ref_123456"
    },
    "customer": {
      "email": "customer@example.com",
      "phone": "08012345678",
      "name": "Sadeeq Doe"
    },
    "transaction": {
      "order_no": "ORD-123456789",
      "order_status": "SUCCESS",
      "order_amount": 5000.00,
      "order_amount_cents": 500000,
      "currency": "NGN",
      "created_time": 1678901200,
      "update_time": 1678901234,
      "reference": "ref_123456",
      "session_id": "sess_123456"
    },
    "payer": {
      "account_number": "0123456789",
      "account_name": "Jane Doe",
      "bank_name": "GTBank"
    },
    "merchant": {
      "merchant_id": "MERCH-123456",
      "business_name": "My Business"
    }
  }
}
Always verify webhook signatures
Validate timestamp to prevent replay attacks
Use HTTPS endpoints only

Security Best Practices

Protect Your API Keys

Never store API keys in your frontend code or public repositories.

Use HTTPS Only

Always use HTTPS for API requests to encrypt data in transit.

Monitor Webhooks

Implement webhook signature verification to ensure webhook authenticity.