Initiate Collection (Checkout)
The Initiate Collection endpoint allows you to request payment from a customer. Use this when you want to collect money from users.
Endpoint
POST /payments/collections/checkouts/initiate
Authentication
Required: Yes (Bearer Token)
Authorization: Bearer YOUR_ACCESS_TOKEN
Request
Request Body
{
"payfrom": "+254712345678",
"amount": "1000.00",
"ref_no": "ORDER-2024-001",
"account_number": "1001",
"callback_url": "https://yourdomain.com/webhook",
"category": "ecommerce",
"app_code": 12345
}
Field Descriptions
| Field | Type | Required | Description |
|---|---|---|---|
payfrom | string | Yes | Customer phone number (E.164 format) |
amount | string | Yes | Payment amount in KES (decimal with 2 places) |
ref_no | string | Yes | Your unique reference number (max 50 chars) |
account_number | string | Yes | Your account number for tracking |
callback_url | string | Yes | URL for transaction notifications |
category | string | No | Transaction category: ecommerce, gaming, adult_content, forex, or other (default) |
app_code | integer | Yes | App code for multi-app scenarios |
Response
Success Response (200 OK)
{
"payfrom": "+254712345678",
"amount": "1000.00",
"ref_no": "ORDER-2024-001",
"account_number": "1001",
"callback_url": "https://yourdomain.com/webhook",
"status": "pending",
"category": "ecommerce",
"created_at": "12/04/2024 14:30:45"
}
Response Fields
| Field | Type | Description |
|---|---|---|
payfrom | string | Customer phone number |
amount | string | Payment amount (KES) |
ref_no | string | Your reference number |
account_number | string | Account number used |
callback_url | string | Webhook notification URL |
status | string | Transaction status |
category | string | Transaction category |
created_at | string | Timestamp (DD/MM/YYYY HH:MM:SS) |
Status Codes
| Code | Status | Description |
|---|---|---|
| 200 | Success | Collection initiated successfully |
| 400 | Bad Request | Invalid parameters |
| 401 | Unauthorized | Invalid or expired token |
| 403 | Forbidden | Merchant not approved or app inactive |
| 404 | Not Found | App not found |
| 500 | Server Error | Internal server error |
Error Responses
400 Bad Request - Invalid Phone Number
{
"detail": "Invalid phone number"
}
400 Bad Request - Invalid Amount
{
"detail": "Invalid amount"
}
401 Unauthorized
{
"detail": "Invalid or expired token"
}
403 Forbidden - Merchant Not Approved
{
"detail": "Your merchant profile is not approved. Please ensure your profile has been verified before initiating checkouts."
}
403 Forbidden - App Inactive
{
"detail": "App is not active"
}
403 Forbidden - Product Not Enabled
{
"detail": "App does not have collections product enabled"
}
403 Forbidden - IP Not Allowed
{
"detail": "Access denied: IP address not allowed"
}
This error occurs when your app has IP restrictions configured and the request is coming from an IP address that is not in the allowed list. To fix this:
- Add your server's IP address to the allowed IPs list in your app settings
- Or remove all IP restrictions if you want to allow requests from any IP address
404 Not Found
{
"detail": "App not found"
}
Examples
- cURL
- Python
- JavaScript
curl -X POST https://sandbox.api.jpay.africa/api/v1/payments/collections/checkouts/initiate \
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." \
-H "Content-Type: application/json" \
-d '{
"payfrom": "+254712345678",
"amount": "1000.00",
"ref_no": "ORDER-2024-001",
"account_number": "1001",
"callback_url": "https://yourdomain.com/webhook",
"category": "ecommerce"
}'
import requests
def initiate_collection(access_token, payfrom, amount, ref_no):
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
}
payload = {
'payfrom': payfrom,
'amount': str(amount),
'ref_no': ref_no,
'account_number': '1001',
'callback_url': 'https://yourdomain.com/webhook',
'category': 'ecommerce'
}
response = requests.post(
'https://sandbox.api.jpay.africa/api/v1/payments/collections/checkouts/initiate',
headers=headers,
json=payload
)
if response.status_code == 200:
return response.json()
else:
print(f"Error: {response.status_code}")
print(response.json())
return None
# Usage
collection = initiate_collection(
access_token='your_token',
payfrom='+254712345678',
amount=1000.00,
ref_no='ORDER-2024-001'
)
async function initiateCollection(accessToken, payfrom, amount, refNo) {
const response = await fetch(
'https://sandbox.api.jpay.africa/api/v1/payments/collections/checkouts/initiate',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
payfrom,
amount: amount.toString(),
ref_no: refNo,
account_number: '1001',
callback_url: 'https://yourdomain.com/webhook',
category: 'ecommerce'
})
}
);
const data = await response.json();
if (response.ok) {
console.log('Collection initiated:', data);
return data;
} else {
console.error('Error:', data);
throw new Error(data.detail);
}
}
// Usage
try {
const collection = await initiateCollection(
'your_token',
'+254712345678',
1000.00,
'ORDER-2024-001'
);
} catch (error) {
console.error('Failed to initiate collection:', error);
}
Collection Categories
Use appropriate categories for collection organization and compliance:
| Category | Description |
|---|---|
ecommerce | E-commerce and online shopping transactions |
gaming | Gaming and entertainment transactions |
adult_content | Adult content related transactions |
forex | Forex and trading transactions |
other | Other transaction types (default) |
Category Usage: Categories help with transaction organization, reporting, and compliance. Choose the category that best matches your business type.
Important Notes
Before initiating collections, ensure:
- ✅ Your merchant account is APPROVED (profile_status = approved)
- ✅ Your app has collections product enabled
- ✅ Your app is ACTIVE
- ✅ Phone number is in E.164 format (e.g., +254712345678)
- ✅ Amount has 2 decimal places (e.g., 1000.00)
- ✅ Your server's IP address is whitelisted in the app's allowed IPs (if IP restrictions are configured)
Best Practices
Phone Number Validation
Always validate and format phone numbers before sending:
import phonenumbers
def format_phone_number(phone):
try:
parsed = phonenumbers.parse(phone, "KE")
if phonenumbers.is_valid_number(parsed):
return phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)
except phonenumbers.NumberParseException:
pass
return None
Error Handling
Implement comprehensive error handling:
def create_collection_with_retry(client, max_retries=3):
for attempt in range(max_retries):
try:
response = requests.post(...)
if response.status_code == 200:
return response.json()
elif response.status_code == 401:
# Token expired, refresh and retry
client.refresh_token()
continue
elif response.status_code == 403:
# Merchant not approved, stop
print("Merchant account not approved")
break
elif response.status_code == 429:
# Rate limited, wait and retry
time.sleep(2 ** attempt)
continue
else:
print(f"Unexpected error: {response.status_code}")
break
except requests.RequestException as e:
print(f"Request failed: {e}")
if attempt < max_retries - 1:
time.sleep(2 ** attempt)
Amount Validation
Validate amounts before sending:
from decimal import Decimal
def validate_amount(amount):
try:
decimal_amount = Decimal(str(amount))
# Check range
if decimal_amount < Decimal('1.00') or decimal_amount > Decimal('999999.99'):
return False, "Amount must be between 1.00 and 999999.99"
# Check decimal places
if decimal_amount.as_tuple().exponent < -2:
return False, "Amount must have maximum 2 decimal places"
return True, decimal_amount
except:
return False, "Invalid amount format"
Webhook Notifications
When a collection is completed, JPay will POST to your callback_url:
{
"event": "collection.completed",
"transaction_id": "COLL-2024-001",
"ref_no": "ORDER-2024-001",
"payfrom": "+254712345678",
"amount": "1000.00",
"status": "completed",
"notes": "Payment completed successfully",
"beneficiary_kyc": {
"first_name": "John",
"middle_name": "Kamau",
"last_name": "Mwangi"
},
"charges": {
"transaction_fee": "25.00",
"commission": "10.00"
},
"timestamp": "2024-04-12T14:35:00Z"
}
Webhook Payload Fields
| Field | Type | Description |
|---|---|---|
event | string | Event type (e.g., collection.completed, collection.failed) |
transaction_id | string | Unique transaction identifier |
ref_no | string | Your reference number provided during initiation |
payfrom | string | Customer phone number in E.164 format |
amount | string | Payment amount in KES (decimal with 2 places) |
status | string | Payment status: completed, failed, pending |
notes | string | Description of the payment status. Provides details about transaction outcome or failure reason |
beneficiary_kyc | object or null | KYC information of the payer. null when status is not completed |
beneficiary_kyc.first_name | string or null | Payer's first name (may be empty) |
beneficiary_kyc.middle_name | string or null | Payer's middle name (may be empty) |
beneficiary_kyc.last_name | string or null | Payer's last name (may be empty) |
charges | object | Transaction charges breakdown |
charges.transaction_fee | string | Transaction fee charged in KES. "0.00" when status is not completed |
charges.commission | string | Commission amount in KES. "0.00" when status is not completed |
timestamp | string | ISO 8601 timestamp of the event |
KYC Availability: The beneficiary_kyc object is only populated when the payment status is completed. For pending or failed transactions, this field will be null. Individual name fields within the KYC object may also be empty depending on the information available from the payment provider.
Charges: The charges object contains transaction fee and commission details. When the payment status is not completed, both transaction_fee and commission will be "0.00".
Next Steps
- List Collections - Retrieve collection history
- Error Handling - Handle API errors
- Best Practices - Integration best practices