POST https://mybundlepay.com/ng/api/v1/card/customers/enroll
KEY USAGE POLICY
Important: You must always begin your integration using your
test_secret_key
. This ensures you can simulate requests and validate
your flows without creating real customers or transactions.
- Use
test_secret_key
for all development, testing, and sandbox calls. - Only switch to
secret_key
(Live Key) after your integration has been fully tested and approved. - Requests with
test_secret_key
return simulated responses and do not persist customers in the live mode. - Requests with
secret_key
(Live) will create real customers and trigger production flows.
⚠️ Going live without first testing may cause failed enrollments or blocked access.
HEADERS
Authorization * string
Pass your {secret_key}
as a Bearer token in the request header to authorize this call.
Content-Type * application/json
All requests must be sent in JSON format.
IP WHITELISTING
For extra security this endpoint checks that the calling IP is whitelisted for your business. The API checks these headers (in order):
CF-Connecting-IP
(useful when behind Cloudflare)X-Forwarded-For
- fallback to the remote IP (
$request->ip()
)
If your IP is not whitelisted or blocked, the API returns a 403 with a descriptive code (examples below).
CF-Connecting-IP: 203.0.113.5
X-Forwarded-For: 203.0.113.5
You can set one or both headers in Postman → Headers tab. If using a reverse proxy, ensure the real client IP is forwarded.
BODY PARAMS
Customer’s first name.
Customer’s last name.
Valid email address of the customer.
2-letter ISO country code (e.g. US
, NG
, GH
).
Government issued ID number (DRIVERS_LICENSE, NIN, VOTERS_CARD, PASSPORT).
Date of birth. Use 20-10-1988
(ISO) to avoid validation errors.
International dialing code (e.g. +1
, +234
, +261
).
Customer’s phone number (without country code).
Example structure:
"identity": {
"type": "PASSPORT",
"image": "https://example.com/id.jpg", // optional (URL)
"number": "A12345678"
}
Note: do not send 3-letter country codes (use 2-letter codes). Some MyBundlePay flows do not require identity.country
— prefer using top-level country
and address.country
.
"address": {
"street": "123 Main Street",
"street2": null,
"city": "New York",
"state": "NY",
"country": "US",
"postal_code": "10001"
}
country must be 2-letter ISO code and in many cases MyBundlePay only allows certain supported countries — if you get a 400 about address.country try removing the field or using a supported country.
Passport style photo of the customer (URL link).
<?php
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "https://mybundlepay.com/ng/api/v1/card/customers/enroll",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
"first_name" => "John",
"last_name" => "Doe",
"email" => "johndoe@example.com",
"country" => "US",
"identification_number" => "A12345678",
"dob" => "01-01-1990",
"phone_country_code" => "+1",
"phone_number" => "5551234567",
"identity" => [
"type" => "PASSPORT",
"image" => "https://example.com/passport.jpg",
"number" => "A12345678"
],
"address" => [
"street" => "123 Main Street",
"street2" => null,
"city" => "New York",
"state" => "NY",
"country" => "US",
"postal_code" => "10001"
],
"photo" => "https://example.com/photo.jpg"
]),
CURLOPT_HTTPHEADER => array(
"Content-Type: application/json",
"Authorization: Bearer {secret_key}",
"CF-Connecting-IP: 203.0.113.5" // Optional - set to your whitelisted IP
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
const axios = require('axios');
const data = {
first_name: "John",
last_name: "Doe",
email: "johndoe@example.com",
country: "US",
identification_number: "A12345678",
dob: "01-01-1990",
phone_country_code: "+1",
phone_number: "5551234567",
identity: { type: "PASSPORT", image: "https://example.com/id.jpg", number: "A12345678" },
address: { street: "123 Main St", street2: null, city: "NY", state: "NY", country: "US", postal_code: "10001" },
photo: "https://example.com/photo.jpg"
};
axios.post("https://mybundlepay.com/ng/api/v1/card/customers/enroll", data, {
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer {secret_key}",
"X-Forwarded-For": "203.0.113.5" // Optional - set to your whitelisted IP
}
}).then(res => {
console.log(res.data);
}).catch(err => {
console.error(err.response ? err.response.data : err.message);
});
Success Response
{
"status": "success",
"message": "Customer created successfully",
"data": {
"id": 1,
"business_id": "f2fca2f5-b3f1-4759-8bb7-b9f0bb9c0060",
"first_name": "John",
"last_name": "Doe",
"email": "johndoe@example.com",
"country": "US",
"phone_country_code": "+1",
"phone_number": "5551234567",
"identification_number": "A12345678",
"dob": "01-01-1990",
"identity_type": "PASSPORT",
"identity_number": "A12345678",
"identity_image": "https://example.com/id.jpg",
"address_street": "123 Main Street",
"address_city": "New York",
"address_state": "NY",
"address_country": "US",
"address_postal_code": "10001",
"photo": "https://example.com/photo.jpg",
"MyBundlePay_customer_id": "f743070f-d37f-4e7e-bddc-4865fe069927",
"MyBundlePay_status": "COMPLETED",
"MyBundlePay_tier": 2,
"raw_response": { /* MyBundlePay response object */ },
"created_at": "2025-09-28T15:34:17Z",
"updated_at": "2025-09-28T15:34:17Z"
}
}
Common Error Responses
Missing token — 401
{
"status": "failed",
"code": "TOKEN_REQUIRED",
"message": "Authorization token is required."
}
Invalid token / business not found — 401 / 404
{
"status": "failed",
"code": "BUSINESS_NOT_FOUND",
"message": "Business not found for this secret key."
}
IP not whitelisted — 403
{
"status": "failed",
"code": "IP_NOT_WHITELISTED",
"message": "Your IP is not authorized for API access.",
"ip": "203.0.113.5"
}
IP blocked — 403
{
"status": "failed",
"code": "IP_BLOCKED",
"message": "Your IP has been blocked from API access.",
"ip": "203.0.113.5"
}
IP status not allowed — 403
{
"status": "failed",
"code": "IP_STATUS_NOT_ALLOWED",
"message": "Your IP status does not permit API access.",
"ip": "203.0.113.5"
}
Invalid secret key — 400
{
"status": "failed",
"message": "Invalid secret key",
"data": null
}
Invalid token format — 401
{
"status": "failed",
"message": "Invalid token format"
}
Validation errors — 400
{
"status": "failed",
"message": {
"email": ["The email field is required."],
"dob": ["The dob must be in YYYY-MM-DD format."]
}
}
Customer already exists (idempotent) — 200
{
"status": "success",
"code": "ALREADY_EXISTS",
"message": "Customer already enrolled for this business",
"data": { /* existing customer object */ }
}
MyBundlePay returned an error — 400
{
"status": "failed",
"message": "Exception contacting MyBundlePay",
"data": {
"status": false,
"message": "Field validation failed",
"error": "HTTP request returned status code 400: { ... }"
}
}
Note: codes like IP_NOT_WHITELISTED
, IP_BLOCKED
are exact and should be used by integrators to handle flows programmatically.