ElyonPay API Documentation
Welcome to the ElyonPay API technical documentation. This reference allows you to integrate Mobile Money payments, Visa, Mastercard and transfers into your application in just a few lines of code.
https://api.elyonpay.net/api (Sandbox) / https://api.elyonpay.org/api (Production)All requests must be sent over HTTPS. Unsecured HTTP requests are rejected. See the Environments section for details.
Key Features
- Unified Mobile Money — MTN MoMo, Orange Money, Wave, Moov, Airtel Money
- Bank Cards — Visa, Mastercard with 3D Secure, PCI DSS level 1 compliant
- Simple Integration — Payment link + iframe, active status verification
- Full Sandbox — Test for free with simulated data
- Multi-currency — XAF, XOF, EUR, USD, GBP, NGN, CDF, KES
- Official SDKs — JavaScript, PHP, Python, Java, React Native, Flutter
Quick Start
Follow these 3 steps to make your first payment:
- Contact us to get your API key
- Install the SDK for your language or use cURL directly
- Call
POST /api/request-to-pay/payment/linkwith the amount and redirect URLs
curl -X POST https://api.elyonpay.org/api/request-to-pay/payment/link \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Content-Type: application/json" \
-H "language: fr" \
-d '{
"amount": 15000,
"user_lang": "fr",
"msisdn": "+237600000000",
"success": "https://yoursite.com/success",
"error": "https://yoursite.com/error"
}'Authentication
The ElyonPay API uses JWT (JSON Web Token) authentication. Obtain your token via the login endpoint, then include it in every request.
| Parameter | Type | Required | Description |
|---|---|---|---|
| username | string | Required | Merchant account email |
| password | string | Required | Account password |
| role | string | Required | ROLE_MERCHANT_ADMIN |
curl -X POST https://api.elyonpay.org/api/login \
-H "Content-Type: application/json" \
-d '{
"username": "you@example.com",
"password": "***",
"role": "ROLE_MERCHANT_ADMIN"
}'Response
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...."
}Usage in HTTP Header
Authorization: Bearer <your_jwt_token>Environments
ElyonPay offers two distinct environments to allow you to develop and test safely before going to production.
Base URLs
| Environment | Base URL | Description |
|---|---|---|
Sandbox | https://api.elyonpay.net/api | No real charges — simulated data |
Production | https://api.elyonpay.org/api | Real transactions |
Mobile Money Test Numbers
| Number | Network | Simulated Behavior |
|---|---|---|
+237600000001 | MTN MoMo | Successful payment |
+237600000002 | MTN MoMo | Insufficient balance |
+237690000001 | Orange Money | Successful payment |
+237690000002 | Orange Money | Simulated timeout |
+221700000001 | Wave | Successful payment |
Request Format
All requests and responses use JSON format. The Content-Type: application/json header is required for requests with a body.
Required Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer {votre_token_jwt} | Required |
Content-Type | application/json | Required (POST) |
Accept | application/json | Optional |
Idempotency-Key | UUID unique | Recommended |
Standard Response Format
{
"success": true,
"data": {
"id": "pay_1A2B3C4D5E6F",
"status": "pending",
"amount": 15000,
"currency": "XAF"
},
"meta": {
"request_id": "req_xxxxxxx",
"timestamp": "2024-03-24T10:15:00Z"
}
}Create a Payment
Generates a secure payment link. The customer is redirected to this link to choose their payment method (Mobile Money, bank card) and complete the transaction.
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| amount | number | Required | Payment amount |
| user_lang | string | Required | Payment interface language (fr, en) |
| msisdn | string | Required | Phone number in international format: +237600000000 |
| success | string | Required | Redirect URL on success |
| error | string | Required | Redirect URL on error |
| webhook | string | Optional | HTTPS URL notified on each transaction state change (see the Webhooks section) |
| onbehalfof | string | number | Optional | Collect on behalf of a sub-merchant: its id or partner code. Platform merchants only (see the Platform Payments section) |
| fees | number | Optional | Platform commission to withhold (same unit as amount). If omitted, the sub-merchant then platform default fees apply |
Response
{
"url": "https://app.elyonpay.org/t/CI032667e43abf11?success=https://www.success.com&error=https://www.error.com"
}Code Examples
cURL
curl -X POST https://api.elyonpay.org/api/request-to-pay/payment/link \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Content-Type: application/json" \
-H "language: fr" \
-d '{
"amount": 15000,
"user_lang": "fr",
"msisdn": "+237600000001",
"success": "https://yoursite.com/success",
"error": "https://yoursite.com/error",
"webhook": "https://yoursite.com/webhooks/elyonpay"
}'Node.js / JavaScript
const response = await fetch('https://api.elyonpay.org/api/request-to-pay/payment/link', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
'language': 'fr'
},
body: JSON.stringify({
amount: 15000,
user_lang: 'fr',
msisdn: '+237600000001',
success: 'https://yoursite.com/success',
error: 'https://yoursite.com/error',
webhook: 'https://yoursite.com/webhooks/elyonpay'
})
});
const data = await response.json();
console.log(data.url); // Payment link to redirect the customerPHP
$ch = curl_init('https://api.elyonpay.org/api/request-to-pay/payment/link');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'language: fr'
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 15000,
'user_lang' => 'fr',
'msisdn' => '+237600000001',
'success' => 'https://yoursite.com/success',
'error' => 'https://yoursite.com/error',
'webhook' => 'https://yoursite.com/webhooks/elyonpay'
])
]);
$response = json_decode(curl_exec($ch), true);
echo $response['url']; // Payment linkTransactions
Retrieve the complete history of your transactions: received payments, refunds, fees.
Returns a paginated list of all your transactions. You can also retrieve details of a specific transaction via its identifier.
Transaction Detail
curl -X GET https://api.elyonpay.org/api/transactions/1553 \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Accept: application/json"Response Example
{
"id": 1553,
"PSPid": "FR033169cbd9c1f1",
"uuid": "FR033169cbd9c1f1",
"creation_date": "2026-03-31T14:27:13+00:00",
"validation_date": null,
"delivery_date": null,
"amount": 100,
"totalCustomer": 100,
"state": "CREATED",
"ticket_type": "SMS_EMAIL",
"operation_code": "03",
"payer_phone_number": "+33782112127",
"beneficiary_phone_number": "+33782112127",
"payer_currency": "EUR",
"beneficiary_currency": "EUR",
"payer_country": "FR",
"beneficiary_country": "FR",
"payment": {
"name": "MOLLIE"
},
"customer": {
"phoneNumber": "+33782112127",
"email": "client@example.com"
},
"merchant": {
"id": 258,
"name": "Ma Boutique",
"city": "Paris",
"address": "10 Rue de la Paix",
"zipCode": "75002",
"type": "individual",
"country_code": "FR",
"country": "France"
},
"transactionStates": [
{
"state": "CREATED",
"date": "2026-03-31T14:27:14+00:00"
}
],
"commission_operateur": 0,
"commission_marchand": 0,
"commission_pays": 0,
"commission_customer": 0,
"deliveryPrice": 0
}Response Fields
| Field | Type | Description |
|---|---|---|
| id | integer | Internal transaction identifier |
| PSPid | string | Unique ElyonPay identifier (e.g. FR033169cbd9c1f1) |
| uuid | string | Unique identifier — same value as PSPid |
| creation_date | datetime | Creation date (ISO 8601 with timezone) |
| validation_date | datetime|null | Payment validation date — null if not yet validated |
| delivery_date | datetime|null | Delivery date — null if not applicable |
| amount | number | Transaction amount in the beneficiary's currency |
| totalCustomer | number | Total amount charged to the customer (fees included) |
| state | string | Current status: CREATED, PENDING, WAITING_FOR_PAYMENT, ACCEPTED, DELIVERED, REJECTED, DECLINED, CANCELLED |
| ticket_type | string | Notification type: SMS_EMAIL, SMS, EMAIL |
| operation_code | string | Internal operation code |
| payer_phone_number | string | Payer's phone number (international format) |
| beneficiary_phone_number | string | Beneficiary's phone number |
| payer_currency | string | Payer's currency (ISO 4217 code: EUR, XAF, XOF...) |
| beneficiary_currency | string | Beneficiary's currency |
| payer_country | string | Payer's country code (ISO 3166: FR, CM, CI...) |
| beneficiary_country | string | Beneficiary's country code |
| payment.name | string | Payment provider used (MOLLIE, MTN, ORANGE...) |
customer Object
| Field | Type | Description |
|---|---|---|
| phoneNumber | string | Customer's phone number |
| string | Customer's email address |
merchant Object
| Field | Type | Description |
|---|---|---|
| id | integer | Merchant identifier |
| name | string | Merchant or business name |
| city | string | Merchant's city |
| address | string | Postal address |
| zipCode | string | Zip code |
| type | string | Account type: individual, company |
| country_code | string | ISO 3166 country code (FR, CM, CI...) |
| country | string | Country name |
transactionStates Array
History of transaction status changes, from oldest to newest.
| Field | Type | Description |
|---|---|---|
| state | string | Status at that point (CREATED, PENDING, WAITING_FOR_PAYMENT, ACCEPTED, DELIVERED...) |
| date | datetime | Status change date |
Fees
| Field | Type | Description |
|---|---|---|
| commission_operateur | number | Telecom operator commission |
| commission_marchand | number | Merchant commission |
| commission_pays | number | Country commission |
| commission_customer | number | Commission applied to the customer |
| deliveryPrice | number | Delivery fees |
Custom Checkout
Custom Checkout lets you build your own payment UI while using ElyonPay APIs directly. Unlike the simple payment link (redirect), you keep full control over the user experience.
4-Step Flow
Fetch payment methods
Call GET /api/public/configuration/payments/{countryCode} to get available methods in the customer's country.
Display your checkout
Show the order summary and payment methods in your own UI. The customer picks how to pay.
Initiate payment
Send the direct payment request (Mobile Money or bank card) with the chosen method.
Poll for status
Query GET /api/transactions/{id} every 3–5 seconds until you get a terminal state (DELIVERED, FAILED, CANCELLED).
Sequence Diagram
sequenceDiagram
participant Customer
participant Merchant
participant ElyonPay
Customer->>Merchant: Browses products
Merchant->>ElyonPay: GET /api/public/configuration/payments/{country}
ElyonPay-->>Merchant: Available payment methods
Customer->>Merchant: Selects payment method
Merchant->>ElyonPay: POST /api/mobile-money/payment/request
ElyonPay-->>Merchant: PaymentResult (transactionId, state)
loop Poll every 3-5s (max 2 min)
Merchant->>ElyonPay: GET /api/transactions/{id}
ElyonPay-->>Merchant: Transaction state
end
Merchant->>Customer: Displays confirmation or error
Payment Methods
Dynamically retrieve the list of available payment methods in a given country. This endpoint is public — no authentication is required.
Path Parameter
| Parameter | Type | Required | Description |
|---|---|---|---|
| countryCode | string | Required | ISO 3166 country code — see table below |
Supported Country Codes
| Code | Country |
|---|---|
CM | Cameroon |
CI | Ivory Coast |
GA | Gabon |
FR | France |
TG | Togo |
BJ | Benin |
Response Example
{
"payments": [
{
"name": "ORANGE_MONEY",
"label": "Orange Money",
"type": "MOBILE_MONEY",
"available": true
},
{
"name": "MTN_MONEY",
"label": "MTN Mobile Money",
"type": "MOBILE_MONEY",
"available": true
},
{
"name": "STRIPE",
"label": "Carte bancaire (Visa / Mastercard)",
"type": "CARD",
"available": true
}
]
}curl https://api.elyonpay.org/api/public/configuration/payments/CMDirect Payment
Initiate a payment directly from your server without redirecting to an ElyonPay page. The customer stays on your site throughout the transaction.
Mobile Money
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| amount | number | Required | Payment amount in local currency |
| user_lang | string | Required | User language (fr, en) |
| merchant_name | string | Required | Merchant name shown to the customer |
| merchant_id | integer | Required | Merchant identifier |
| currency | string | Required | ISO 4217 currency code (XAF, XOF, EUR...) |
| country_name | string | Required | Customer's country name |
| payment_method | string | Required | Must match the name field returned by GET /api/public/configuration/payments/{countryCode} |
| msisdn | string | Required | Customer phone number (international format) |
| transaction | string | Optional | External transaction reference (optional) |
cURL
curl -X POST https://api.elyonpay.org/api/mobile-money/payment/request \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Content-Type: application/json" \
-d '{
"amount": 15000,
"user_lang": "fr",
"merchant_name": "Ma Boutique",
"merchant_id": 207,
"currency": "XAF",
"country_name": "Cameroon",
"payment_method": "ORANGE_MONEY",
"msisdn": "+237690000001"
}'Node.js / JavaScript
const response = await fetch('https://api.elyonpay.org/api/mobile-money/payment/request', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 15000,
user_lang: 'fr',
merchant_name: 'Ma Boutique',
merchant_id: 207,
currency: 'XAF',
country_name: 'Cameroon',
payment_method: 'ORANGE_MONEY',
customer_msisdn: '+237690000001'
})
});
const result = await response.json();
// Start polling result.data.uuid for statusPHP
$ch = curl_init('https://api.elyonpay.org/api/mobile-money/payment/request');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 15000,
'user_lang' => 'fr',
'merchant_name' => 'Ma Boutique',
'merchant_id' => 207,
'currency' => 'XAF',
'country_name' => 'Cameroon',
'payment_method' => 'ORANGE_MONEY',
'customer_msisdn' => '+237690000001'
])
]);
$response = json_decode(curl_exec($ch), true);
// Start polling $response['data']['uuid'] for statusBank Card
Same payload as the Mobile Money payment, with two additional parameters to control the post-payment redirect.
Additional Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| success | string | Optional | Redirect URL after a successful payment. The customer will be redirected to {success}?id={uuid}&status=pending |
| error | string | Optional | Redirect URL after a failed or cancelled payment. The customer will be redirected to {error}?id={uuid}&status=cancelled |
If these parameters are not provided, the customer will be redirected to the default ElyonPay transaction page.
Important: never rely solely on the redirect URL (success/error) to confirm a payment. A user can manually modify the URL. Always verify the status server-side via GET /api/transactions/{uuid}.
Response — PaymentResult
{
"success": true,
"data": {
"PSPid": "tr_aHYQAw4rudrttLDwtdKTJ",
"PSPnotifyId": "tr_aHYQAw4rudrttLDwtdKTJ",
"PSPtxnId": "tr_aHYQAw4rudrttLDwtdKTJ",
"url": "https://www.mollie.com/checkout/credit-card/session/aHYQAw4rudrttLDwtdKTJ",
"uuid": "CM06306a438d0958",
"PSPMessage": null,
"PSPstatus": ""
}
}Response Fields
| Field | Type | Description |
|---|---|---|
| success | boolean | Whether the request succeeded |
| data.PSPid | string | Payment Service Provider identifier |
| data.PSPnotifyId | string | PSP notification identifier |
| data.PSPtxnId | string | PSP transaction identifier |
| data.url | string | Payment URL (card redirect if applicable) |
| data.uuid | string | Unique ElyonPay identifier — use this for polling |
| data.PSPMessage | string|null | Message returned by the PSP (null if none) |
| data.PSPstatus | string | Status returned by the PSP |
Status Polling
After initiating a direct payment, periodically poll the API to get the transaction result. Polling is the reference mechanism; optional webhooks are also available for real-time notifications.
Terminal States
| State | Result | Description |
|---|---|---|
| DELIVERED | Success | Payment confirmed, funds transferred |
| FAILED | Failure | Payment failed (insufficient balance, network error...) |
| DECLINED | Failure | Payment declined by the operator |
| CANCELLED | Failure | Cancelled by the customer or system |
| PENDING | Processing | Transaction in progress — keep polling |
Complete Example — Node.js / Express
This example covers all 4 Custom Checkout steps: payment methods, display, direct payment and status polling.
const express = require('express');
const app = express();
app.use(express.json());
const API = 'https://api.elyonpay.org/api';
let token = ''; // Obtained via POST /api/login
// Helper: poll transaction status
async function pollStatus(txId, maxMs = 120000, intervalMs = 4000) {
const start = Date.now();
while (Date.now() - start < maxMs) {
const res = await fetch(`${API}/transactions/${txId}`, {
headers: { 'Authorization': 'Bearer ' + token }
});
const tx = await res.json();
const terminal = ['DELIVERED', 'FAILED', 'DECLINED', 'CANCELLED'];
if (terminal.includes(tx.state)) return tx;
await new Promise(r => setTimeout(r, intervalMs));
}
throw new Error('Polling timeout');
}
// Step 1 — Get payment methods for customer's country
app.get('/payment-methods/:country', async (req, res) => {
const methods = await fetch(
`${API}/public/configuration/payments/${req.params.country}`
).then(r => r.json());
res.json(methods);
});
// Steps 2-3-4 — Customer picks method, initiate payment, poll result
app.post('/pay', async (req, res) => {
const { payment_method, phone, merchant_id, amount } = req.body;
// Step 3 — Initiate direct payment
const payment = await fetch(`${API}/mobile-money/payment/request`, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount,
user_lang: 'fr',
merchant_name: 'Ma Boutique',
merchant_id,
currency: 'XAF',
country_name: 'Cameroon',
payment_method,
customer_msisdn: phone
})
}).then(r => r.json());
// Step 4 — Poll for terminal state
try {
const tx = await pollStatus(payment.data.uuid);
if (tx.state === 'DELIVERED') {
res.json({ success: true, transaction: tx });
} else {
res.json({ success: false, state: tx.state, transaction: tx });
}
} catch (err) {
res.status(408).json({ success: false, error: 'Payment timeout' });
}
});
app.listen(3000);Official SDKs & Plugins
Official SDKs and plugins are available to integrate ElyonPay into your projects. They automatically handle authentication, API calls and payment status verification.
Official PHP SDK for integrating the ElyonPay API. Zero external dependencies — uses only native cURL.
Official WooCommerce plugin. Accept Mobile Money and bank cards directly from your WordPress store.
Official SDK for integrating Custom Checkout into your React and Vue.js apps. Includes hooks, composables and automatic polling.
Integration Flow
ElyonPay uses a 6-step model based on a payment link and active status verification (pull model). No webhook infrastructure is required — optional webhooks are however available as a complement.
Get a payment link
Your backend calls POST /api/request-to-pay/payment/link with the amount and redirect URLs (success / error). The API returns a unique payment URL.
Display the payment iframe
Redirect the customer to the returned URL, or embed it in an iframe. The customer chooses their payment method (Mobile Money, bank card) and enters their details.
Payment validation
The customer confirms the payment on the operator side (USSD code for Mobile Money, 3D Secure for cards). ElyonPay processes the transaction.
Redirect to your backend
Once the payment is processed, the customer is redirected to your success or error URL depending on the result. The transaction UUID is passed in the redirect URL.
Fetch the transaction status
Your backend calls GET /api/transactions/{uuid} to retrieve the definitive transaction status. This server-side check is essential — never rely solely on the redirect URL.
Display confirmation to the customer
Based on the returned status (DELIVERED, REJECTED, CANCELLED...), display the appropriate confirmation or error page to your customer.
Diagram — Payment Initiation
sequenceDiagram
participant Customer
participant Merchant
participant ElyonPay
Customer->>Merchant: Finalizes cart
Customer->>Merchant: Selects "Pay"
Merchant->>ElyonPay: POST /api/login
ElyonPay-->>Merchant: Returns JWT token
Merchant->>ElyonPay: POST /api/request-to-pay/payment/link
ElyonPay-->>Merchant: Returns payment URL
Merchant->>Customer: Displays payment page (iframe or redirect)
Customer->>ElyonPay: Selects payment method
Customer->>ElyonPay: Completes transaction
ElyonPay-->>Customer: Shows confirmation popup
Diagram — Payment Result
sequenceDiagram
participant Customer
participant Merchant
participant ElyonPay
Customer->>ElyonPay: Transaction completed
alt Successful payment
ElyonPay->>Merchant: Redirect to success URL
else Failed payment
ElyonPay->>Merchant: Redirect to error URL
end
Merchant->>ElyonPay: GET /api/transactions/[ID_TRANSACTION]
ElyonPay-->>Merchant: Returns transaction status
Merchant->>Merchant: Updates order status
Merchant->>Customer: Shows order confirmation
Implementation example (steps 1 → 5)
Node.js / Express
// 1. Create payment link
app.post('/checkout', async (req, res) => {
const response = await fetch('https://api.elyonpay.org/api/request-to-pay/payment/link', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
'language': 'fr'
},
body: JSON.stringify({
amount: req.body.amount,
user_lang: 'fr',
msisdn: req.body.phone,
success: 'https://yoursite.com/payment/done',
error: 'https://yoursite.com/payment/error'
})
});
const { url } = await response.json();
res.redirect(url); // 2. Redirect customer to payment page
});
// 4 → 5. Handle redirect & verify status
app.get('/payment/done', async (req, res) => {
// The transaction uuid is available from your order context
const tx = await fetch(`https://api.elyonpay.org/api/transactions/${uuid}`, {
headers: { 'Authorization': 'Bearer ' + token }
}).then(r => r.json());
if (tx.state === 'DELIVERED') {
// 6. Show confirmation
res.render('success', { transaction: tx });
} else {
res.render('pending', { transaction: tx });
}
});Mobile Money
ElyonPay unifies 5 African Mobile Money networks under a single API. Each network has its own operator codes and country coverage.
Supported Networks
| Method Code | Network | Countries | Currencies |
|---|---|---|---|
mtn_mobile_money | MTN MoMo | CM, NG, CI, GH, UG | XAF, NGN, XOF |
orange_money | Orange Money | CM, CI, SN, ML | XAF, XOF |
wave | Wave | SN, CI, BF, ML | XOF |
moov_money | Moov Africa | BJ, TG, BF, NE | XOF |
airtel_money | Airtel Money | KE, TZ, UG, ZM, CD | KES, TZS, UGX, CDF |
Coverage: Cameroon, Nigeria, Ivory Coast, Ghana, Uganda, Senegal, Mali, Burkina Faso, Benin, Togo, Niger, Kenya, Tanzania, Zambia, Democratic Republic of Congo.
Bank Cards
Accept Visa and Mastercard payments with 3D Secure, tokenization and PCI DSS level 1 compliance.
Test Cards (Sandbox)
| Card Number | Expiry | CVV | Behavior |
|---|---|---|---|
4242 4242 4242 4242 | 12/28 | 123 | Successful payment |
4000 0000 0000 0002 | 12/28 | 123 | Card declined |
4000 0000 0000 3220 | 12/28 | 123 | 3D Secure required |
5555 5555 5555 4444 | 12/28 | 123 | Mastercard success |
Platform Payments (Split)
A platform (marketplace) can collect a payment on behalf of its merchants. ElyonPay automatically splits the funds between the sub-merchant and the platform: the sub-merchant receives the net amount and the platform withholds its commission, in a single customer-facing transaction.
Step 1 — Create your sub-merchants
Before collecting on behalf of a merchant, you must register it as a sub-merchant of your platform. Two methods are available:
- From the dashboard — in the Sub-merchants section of your platform dashboard, add a merchant via the form (identity, country, default fees). Ideal for manual management.
- Via the API — create your sub-merchants programmatically with the dedicated endpoint below. Ideal for automated onboarding.
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Sub-merchant business name |
| country_code | string | Required | ISO 3166 country code (e.g. CM) |
| city | string | Required | City |
| address | string | Required | Address |
| type | string | Required | Business type (e.g. RETAIL) |
| owner_email | string | Required | Account owner's email |
| owner_phone_number | string | Required | Owner's phone number |
| owner_first_name | string | Required | Owner's first name |
| owner_last_name | string | Required | Owner's last name |
| owner_password | string | Required | Initial password for the owner account |
| default_marketplace_fee_percent | number | Optional | Default commission (%) withheld on this sub-merchant's payments. Otherwise the platform default fees apply |
curl -X POST https://api.elyonpay.org/api/merchant/sub-merchants \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Boutique ABC",
"country_code": "CM",
"city": "Douala",
"address": "123 Rue Commerce",
"type": "RETAIL",
"owner_email": "owner@boutique-abc.com",
"owner_phone_number": "+237600000000",
"owner_first_name": "Jean",
"owner_last_name": "Dupont",
"owner_password": "s3cur3-p4ssword",
"default_marketplace_fee_percent": 5.00
}'{
"merchant_id": 725,
"uuid": "a1b2c3d4-...",
"name": "Boutique ABC",
"owner_email": "owner@boutique-abc.com",
"owner_phone": "+237600000000",
"kyb_required": true,
"default_fee_percent": "5.00"
}Note the returned merchant_id: it is the identifier you will pass in onbehalfof to collect for this merchant. The sub-merchant must have a configured payout account (to receive the funds); its KYB verification may also be required before going live.
Manage your sub-merchants
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/merchant/sub-merchants | List your sub-merchants |
| GET | /api/merchant/sub-merchants/{id} | Get a sub-merchant |
| PATCH | /api/merchant/sub-merchants/{id}/fees | Update its default commission |
Step 2 — Collect on behalf of a sub-merchant
Create the payment as usual (see Create a Payment), adding the onbehalfof parameter with the sub-merchant's id or partner code. ElyonPay then creates a split payment.
curl -X POST https://api.elyonpay.org/api/request-to-pay/payment/link \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Content-Type: application/json" \
-H "language: fr" \
-d '{
"amount": 15000,
"user_lang": "fr",
"msisdn": "+237600000001",
"success": "https://yoursite.com/success",
"error": "https://yoursite.com/error",
"webhook": "https://yoursite.com/webhooks/elyonpay",
"onbehalfof": 725
}'How the commission is computed
The commission withheld by the platform is resolved in this priority order:
- The
feesparameter passed in the call (exact amount, takes priority when present). - Otherwise, the sub-merchant's default commission (default_marketplace_fee_percent).
- Otherwise, the default commission configured on the platform.
- If no source is available, the request is rejected (400 error).
The commission must be positive and strictly lower than the amount, and respect the caps defined on the platform. The sub-merchant receives amount − commission, the platform receives the commission.
Sub-merchant collecting directly
A sub-merchant can also initiate a payment itself, using its own API key, without going through the platform. In that case, do not add onbehalfof: the split to the parent platform applies automatically, with the same commission rules.
Error Codes
| Situation | Code |
|---|---|
| The calling account is not a platform, or the sub-merchant belongs to another platform | 403 |
| Sub-merchant not found (id or partner code) | 404 |
| Invalid commission (negative, ≥ amount, cap exceeded) or no payout account configured | 400 |
Webhooks
ElyonPay can notify your server in real time whenever a transaction changes state, via a server-to-server HTTP POST request. Webhooks are optional: they complement the pull model (polling) but do not replace it.
Enabling Webhooks
No global configuration is needed: the webhook URL is provided per transaction, via the optional webhook parameter when creating the payment. The URL must be a valid, publicly reachable HTTPS URL.
curl -X POST https://api.elyonpay.org/api/request-to-pay/payment/link \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Content-Type: application/json" \
-H "language: fr" \
-d '{
"amount": 15000,
"user_lang": "fr",
"msisdn": "+237600000000",
"success": "https://yoursite.com/success",
"error": "https://yoursite.com/error",
"webhook": "https://yoursite.com/webhooks/elyonpay"
}'Notified Events
A notification is sent each time the transaction reaches one of the following states:
| Event | State | Description |
|---|---|---|
| transaction.pending | PENDING | Payment initiated, awaiting confirmation |
| transaction.delivered | DELIVERED | Payment confirmed, funds transferred to the merchant |
| transaction.declined | DECLINED | Payment declined by the operator |
| transaction.cancelled | CANCELLED | Payment cancelled by the customer or the system |
Notification Format
Each notification is a JSON POST request, sent with the following headers:
| Header | Description |
|---|---|
| Content-Type | Always application/json |
| X-Mip-Event | Event name (e.g. transaction.delivered) |
| X-Mip-Delivery | Unique notification identifier — use it for deduplication |
| X-Mip-Attempt | Attempt number (1 to 5) |
Payload Example
{
"event": "transaction.delivered",
"created_at": "2026-07-08T18:45:12+02:00",
"data": {
"transaction": "CI032667e43abf11",
"state": "DELIVERED",
"amount": 15000,
"currency": "XAF",
"operation_code": "OP-2026-000123",
"merchant_id": 42,
"creation_date": "2026-07-08T18:40:03+02:00",
"validation_date": "2026-07-08T18:44:57+02:00",
"delivery_date": "2026-07-08T18:45:10+02:00"
}
}For marketplace transactions (payment split between several merchants), the payload also contains a data.split array listing each merchant's share (merchant_id, amount) — see Platform Payments.
Acknowledgement and Retries
Your server must respond with a 2xx HTTP status code within 10 seconds. Any other response (4xx, 5xx, timeout, network error) is considered a failure and triggers a retry according to the following schedule:
| Attempt | Delay after previous failure |
|---|---|
| 1 | Immediate (on state change) |
| 2 | 3 seconds |
| 3 | 30 seconds |
| 4 | 60 seconds |
| 5 | 90 seconds |
After 5 unsuccessful attempts, the notification is permanently abandoned. The transaction status always remains available via GET /api/transactions/{uuid}.
Receiver Example (Node.js / Express)
app.post('/webhooks/elyonpay', express.json(), async (req, res) => {
// 1. Acknowledge immediately — heavy processing must happen async
res.sendStatus(200);
const event = req.headers['x-mip-event']; // e.g. "transaction.delivered"
const deliveryId = req.headers['x-mip-delivery']; // unique per notification
// 2. Deduplicate: the same delivery may be received more than once
if (await alreadyProcessed(deliveryId)) return;
// 3. Verify the state server-side before acting on it
const { transaction } = req.body.data;
const status = await fetch(
'https://api.elyonpay.org/api/transactions/' + transaction,
{ headers: { Authorization: 'Bearer ' + token } }
).then((r) => r.json());
if (status.state === 'DELIVERED') {
await fulfillOrder(transaction);
}
});Best Practices
- Respond quickly — Return the 2xx status before any long processing: beyond 10 seconds, the notification is considered failed and will be retried.
- Deduplicate — Store the X-Mip-Delivery header and ignore already-processed notifications — retries can cause duplicates.
- Verify server-side — Use the webhook as a signal, then confirm the state via GET /api/transactions/{uuid} before any business action.
- Keep polling as a safety net — If your server is unavailable for more than ~3 minutes, the notification is abandoned: polling remains the source of truth.
Payment Statuses
Each payment goes through several statuses during its lifecycle.
State Transition Diagram
stateDiagram-v2
[*] --> CREATED
CREATED --> PENDING: Payment initiated
PENDING --> WAITING_FOR_PAYMENT: Awaiting customer
WAITING_FOR_PAYMENT --> ACCEPTED: Payment confirmed
PENDING --> ACCEPTED: Direct approval
PENDING --> REJECTED: Validation failed
PENDING --> DECLINED: Payment issue
WAITING_FOR_PAYMENT --> DECLINED: Payment issue
WAITING_FOR_PAYMENT --> CANCELLED: User cancellation
ACCEPTED --> DELIVERED: Funds transferred
ACCEPTED --> CANCELLED: Administrative action
REJECTED --> [*]
DECLINED --> [*]
CANCELLED --> [*]
DELIVERED --> [*]
Error Codes
In case of error, the API returns a JSON object with a machine error code and a readable message.
{
"success": false,
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "The customer's Mobile Money balance is insufficient.",
"http_status": 402
}
}Error Table
| Error Code | HTTP | Description |
|---|---|---|
| UNAUTHORIZED | 401 | Missing or invalid API key |
| INVALID_AMOUNT | 400 | Invalid amount or below minimum allowed |
| CURRENCY_NOT_SUPPORTED | 400 | Currency not supported for this network |
| PHONE_INVALID | 400 | Invalid or unregistered phone number |
| INSUFFICIENT_FUNDS | 402 | Insufficient balance on client side |
| OPERATOR_TIMEOUT | 408 | Mobile Money operator did not respond in time |
| DUPLICATE_REQUEST | 409 | Duplicate request — use Idempotency-Key |
| PAYMENT_NOT_FOUND | 404 | Payment not found |
| RATE_LIMIT_EXCEEDED | 429 | Too many requests — retry in a few seconds |
| INTERNAL_ERROR | 500 | ElyonPay internal error — contact support |
Supported Currencies
| Code | Currency | Minimum Amount | Methods |
|---|---|---|---|
XAF | Franc CFA BEAC | 100 XAF | MTN MoMo, Orange Money, Carte |
XOF | Franc CFA BCEAO | 100 XOF | Wave, Moov, Orange Money, MTN, Carte |
EUR | Euro | 0.50 EUR | Carte (Visa, Mastercard), PayPal |
USD | US Dollar | 0.50 USD | Carte, PayPal |
GBP | British Pound | 0.30 GBP | Carte |
NGN | Nigerian Naira | 100 NGN | MTN MoMo NG |
KES | Kenyan Shilling | 10 KES | Airtel Money |
CDF | Congolese Franc | 500 CDF | Airtel Money |
Sandbox & Tests
The sandbox environment lets you test the complete integration without making real payments. Connect to api.elyonpay.net — no real charges will be made.
Test Accounts by Country
Use these credentials to test the integration on the sandbox environment. Each account is associated with a specific country and currency.
| Phone | Password | |
|---|---|---|
+237683191284 | demo+23733@elyonpay.com | CompteDemo1 |
| Phone | Password | |
|---|---|---|
+2250758789285 | demo+225@elyonpay.com | CompteDemo1 |
| Phone | Password | |
|---|---|---|
+33895555555 | demo+33@elyonpay.com | CompteDemo1 |
Sandbox Limits
- Sandbox transactions incur no fees
- Data is reset every 30 days
- Limit of 1,000 requests/hour per test key
Go to Production
- Complete KYB (Know Your Business) verification in your dashboard
- Switch the base URL from
api.elyonpay.nettoapi.elyonpay.org - Verify that your redirect URLs (success / error) use HTTPS
- Configure your low balance alerts
