Skip to main content

Payments API Overview

The JPay Africa Payments API allows you to process collections and payouts securely and efficiently.

API Endpoints

Collections

Process incoming payments from customers:

  • Initiate Collection: POST /collections/checkouts/initiate
  • List Collections: GET /collections/checkouts
  • Get Collection Details: GET /collections/checkouts/{checkout_id}

Payouts

Send payments to beneficiaries:

  • Initiate Payout: POST /payouts/initiate
  • List Payouts: GET /payouts
  • Get Payout Details: GET /payouts/{payout_id}

Key Concepts

Collection (Checkout)

A collection represents an incoming payment request. Customers are charged and funds are credited to your collection wallet.

Status: pending | processing | completed | failed

Payout

A payout represents an outgoing payment. Funds are debited from your payout wallet and sent to beneficiaries.

Status: pending | processing | completed | failed

Wallet Types

  • Collection Wallet: Receives incoming payments
  • Payout Wallet: Sends outgoing payments

Requirements

To use the Payments API:

  1. ✅ Merchant profile must be APPROVED
  2. ✅ App must have the required product enabled
  3. ✅ Valid access token with authentication
  4. ✅ Correct phone number format (E.164)

Transaction Flow

Collections Flow

1. Initiate Collection

2. Customer receives payment prompt

3. Payment processed

4. Funds credited to collection wallet

5. Webhook notification sent

Payouts Flow

1. Initiate Payout

2. Funds debited from payout wallet

3. Payment processed

4. Beneficiary receives funds

5. Webhook notification sent

Common Parameters

Phone Numbers

All phone numbers must be in E.164 format:

  • Format: +<country_code><phone_number>
  • Example: +254712345678
  • Kenya Code: +254

Amounts

All amounts are in KES (Kenyan Shilling):

  • Format: Decimal number with 2 decimal places
  • Example: 1000.00
  • Min: 1.00
  • Max: 999999.99

Reference Numbers

Reference numbers are unique identifiers for tracking:

  • Format: Alphanumeric string (max 50 characters)
  • Example: ORDER-2024-001
  • Purpose: Link transactions to your internal orders

Error Handling

All API responses follow standard HTTP status codes:

CodeMeaningAction
200SuccessProceed normally
400Bad RequestCheck request parameters
401UnauthorizedVerify authentication
403ForbiddenCheck permissions/status
404Not FoundVerify resource ID
429Too Many RequestsImplement backoff
500Server ErrorRetry with backoff

For detailed error information, see Error Handling.

Rate Limits

  • Requests per minute: 60
  • Burst limit: 100
  • Daily limit: No limit (per merchant)

Pagination

List endpoints support pagination:

ParameterTypeDefaultMax
pageinteger1N/A
page_sizeinteger20100

Example

GET /collections/checkouts?page=2&page_size=50

Filtering

List endpoints support filtering:

ParameterTypeDescription
statusintegerFilter by status
date_fromstringStart date (YYYY-MM-DD)
date_tostringEnd date (YYYY-MM-DD)

Example

GET /collections/checkouts?status=1&date_from=2024-01-01&date_to=2024-12-31

Webhooks

Get real-time notifications for transaction events:

  • Endpoint: Your callback URL
  • Method: POST
  • Payload: JSON with transaction details

Webhook Events

  • collection.created - Collection initiated
  • collection.completed - Collection successful
  • collection.failed - Collection failed
  • payout.created - Payout initiated
  • payout.completed - Payout successful
  • payout.failed - Payout failed

Webhooks are sent to your callback URL when transactions are completed. See endpoint documentation for webhook payload examples.


Webhook Security & Signature Verification

To prevent spoofing, replay attacks, and ensure webhook requests originate from JPay, you must verify the webhook signature on every callback request.

JPay signs each webhook payload with your merchant App Secret (or callback signing secret) and includes the signature and timestamp in the request headers:

HeaderDescription
X-JPay-SignatureThe hex-encoded HMAC-SHA256 signature of the timestamp and payload.
X-JPay-TimestampThe Unix timestamp (in seconds) of when the request was dispatched.

Verification Steps

  1. Extract Headers: Retrieve the values of the X-JPay-Signature and X-JPay-Timestamp headers.
  2. Prevent Replay Attacks: Compute the age of the request (current_time - timestamp). We recommend rejecting any requests older than 5 minutes (300 seconds).
  3. Build the Signature Material: Concatenate the string value of the timestamp, a literal period ., and the raw, unparsed request body bytes:
    signature_material = timestamp + "." + raw_body_bytes
  4. Compute the Expected Signature: Calculate the hex HMAC-SHA256 hash of the signature material using your merchant App Secret as the key.
  5. Constant-Time Comparison: Use a constant-time comparison function (to avoid timing attacks) to verify if your calculated signature matches the X-JPay-Signature header value.

Verification Examples

const crypto = require('crypto');

function verifyWebhook(req, res, next) {
const signature = req.headers['x-jpay-signature'];
const timestamp = req.headers['x-jpay-timestamp'];

// 1. Ensure headers exist
if (!signature || !timestamp) {
return res.status(401).send('Missing security headers');
}

// 2. Prevent replay attacks (5 minute threshold)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
return res.status(401).send('Request timestamp too old');
}

// 3. Reconstruct signature material (req.rawBody must be a Buffer containing the raw body)
const appSecret = process.env.JPAY_APP_SECRET;
const signatureMaterial = Buffer.concat([
Buffer.from(`${timestamp}.`),
req.rawBody // Ensure your Express app has a middleware to preserve req.rawBody
]);

// 4. Compute expected signature
const expectedSignature = crypto
.createHmac('sha256', appSecret)
.update(signatureMaterial)
.digest('hex');

// 5. Constant-time comparison
const isMatch = crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);

if (!isMatch) {
return res.status(401).send('Invalid signature');
}

next();
}

Next Steps