Back to blog Tutoriel

Complete Guide: Integrate the ElyonPay API with PHP

Joffrey Gohin ยท February 2, 2026 ยท 4 min read

Integrating Mobile Money payments is essential for any business operating in West and Central Africa. This technical guide walks you through integrating the ElyonPay API with PHP step by step: JWT authentication, payment link creation, and transaction verification. By the end, you'll be able to accept Orange Money, Wave, MTN MoMo, and card payments across 14 African countries.

1Why Integrate the ElyonPay API

The ElyonPay API provides a unified interface to accept Mobile Money payments (Orange Money, Wave, MTN MoMo, Moov Africa, Airtel Money) and card payments (Visa, Mastercard with 3D Secure) across 14 African countries. The integration model is based on payment links: your PHP server creates a link, redirects the customer to a secure ElyonPay page, then verifies the transaction status.

This "payment link + pull" model greatly simplifies integration: no need to handle PCI DSS compliance, no sensitive data handling server-side, and the payment page is optimized for each operator. Supported currencies include XAF, XOF, EUR, USD, GBP, NGN, KES, and CDF.

2Prerequisites and Environments

Before getting started, make sure you have:

  1. An ElyonPay merchant account with API credentials (username and password)
  2. PHP 7.4 or higher with the cURL extension enabled
  3. A valid SSL certificate on your server (HTTPS required)

The API uses two separate environments:

EnvironmentBase URLDescription
Sandboxhttps://api.elyonpay.net/apiTest transactions, no real debits
Productionhttps://api.elyonpay.org/apiLive transactions

Store your credentials in environment variables or a .env file (never in source code):

env
ELYONPAY_API_URL=https://api.elyonpay.net/api
ELYONPAY_USERNAME=your_username
ELYONPAY_PASSWORD=your_password

3JWT Authentication

The API uses JWT (JSON Web Token) authentication. You must first obtain a token via the POST /api/login endpoint, then include it in the Authorization: Bearer header of all your requests.

php
<?php
$apiUrl = getenv('ELYONPAY_API_URL');

function getToken(): string
{
    global $apiUrl;

    $ch = curl_init("$apiUrl/login");
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
        CURLOPT_POSTFIELDS     => json_encode([
            'username' => getenv('ELYONPAY_USERNAME'),
            'password' => getenv('ELYONPAY_PASSWORD'),
            'role'     => 'ROLE_MERCHANT_ADMIN',
        ]),
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode !== 200) {
        throw new \RuntimeException("Login failed (HTTP $httpCode)");
    }

    $data = json_decode($response, true);
    return $data['token'];
}

The JWT token has a limited lifespan. In production, cache it (Redis, APCu) and renew before expiration. The ROLE_MERCHANT_ADMIN role grants access to all payment operations.

4Create a Payment Link

The main operation is creating a payment link via POST /api/request-to-pay/payment/link. You send the amount, customer phone number, language, and redirect URLs:

php
function createPaymentLink(string $token, array $order): string
{
    global $apiUrl;

    $ch = curl_init("$apiUrl/request-to-pay/payment/link");
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            "Authorization: Bearer $token",
            'Content-Type: application/json',
            'Idempotency-Key: ' . $order['order_id'],
        ],
        CURLOPT_POSTFIELDS     => json_encode([
            'amount'      => $order['amount'],
            'user_lang'   => 'en',
            'msisdn'      => $order['phone'],
            'success_url' => 'https://yoursite.com/payment-success',
            'error_url'   => 'https://yoursite.com/payment-failed',
        ]),
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode !== 200 && $httpCode !== 201) {
        throw new \RuntimeException("Payment link failed (HTTP $httpCode): $response");
    }

    $data = json_decode($response, true);
    return $data['data']['payment_url'];
}

The Idempotency-Key header prevents duplicates on network retries โ€” use your unique order reference. The user_lang parameter (fr/en) sets the payment page language. The msisdn field is the customer's phone number in international format.

5Redirect the Customer

Once you have the payment link, redirect the customer to that URL. They complete payment on the secure ElyonPay page (Mobile Money or card), then get redirected to your success_url or error_url:

php
// In your order controller
$token = getToken();
$paymentUrl = createPaymentLink($token, [
    'amount'   => 5000,
    'phone'    => '+237691234567',
    'order_id' => 'ORD-2026-042',
]);

// Save order as pending before redirect
saveOrderPaymentPending('ORD-2026-042');

// Redirect customer
header("Location: $paymentUrl");
exit;

Important: Never rely solely on the redirect to success_url to validate a payment. A user could manually access that URL. Always verify the transaction status server-side (next section).

6Verify Transaction Status

The ElyonPay API uses a "pull" model: it's your responsibility to verify the transaction status via GET /api/transactions/{id}. Call this endpoint when the customer is redirected to your success_url:

php
function getTransaction(string $token, string $transactionId): array
{
    global $apiUrl;

    $ch = curl_init("$apiUrl/transactions/$transactionId");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            "Authorization: Bearer $token",
            'Accept: application/json',
        ],
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode !== 200) {
        throw new \RuntimeException("Transaction fetch failed (HTTP $httpCode)");
    }

    return json_decode($response, true);
}

// Verify after redirect
$transaction = getTransaction($token, $transactionId);
$state = $transaction['data']['state'];

// Statuses: CREATED โ†’ PENDING โ†’ WAITING_FOR_PAYMENT โ†’ ACCEPTED โ†’ DELIVERED
if ($state === 'DELIVERED') {
    markOrderAsPaid('ORD-2026-042');
} elseif (in_array($state, ['REJECTED', 'DECLINED', 'CANCELLED'])) {
    markOrderAsFailed('ORD-2026-042');
}

Only consider a payment successful at the DELIVERED status. You can also list all your transactions with GET /api/transactions (paginated) for accounting reconciliation.

7Error Handling

The API returns JSON errors with machine-readable codes. Here are the most common errors:

Error CodeMeaningRecommended Action
INSUFFICIENT_FUNDSInsufficient Mobile Money balanceInform customer to top up
PHONE_INVALIDUnregistered / invalid numberAsk customer to verify their number
AMOUNT_TOO_LOWAmount below minimumCheck minimums per currency
TOKEN_EXPIREDJWT token expiredRenew token and retry
php
function handleApiError(string $responseBody, int $httpCode): void
{
    $error = json_decode($responseBody, true);
    $code = $error['code'] ?? 'UNKNOWN';

    match ($code) {
        'INSUFFICIENT_FUNDS' => throw new PaymentException('Insufficient balance'),
        'PHONE_INVALID'      => throw new PaymentException('Invalid phone number'),
        'TOKEN_EXPIRED'      => throw new AuthException('Token expired'),
        default              => throw new ApiException("API error [$code]: " . ($error['message'] ?? '')),
    };
}

8Complete Helper Class

Here's a reusable class that encapsulates all operations. You can integrate it into your framework (Laravel, Symfony, etc.):

php
class ElyonPayClient
{
    private string $apiUrl;
    private ?string $token = null;

    public function __construct(string $apiUrl)
    {
        $this->apiUrl = rtrim($apiUrl, '/');
    }

    public function authenticate(string $username, string $password): void
    {
        $response = $this->request('POST', '/login', [
            'username' => $username,
            'password' => $password,
            'role'     => 'ROLE_MERCHANT_ADMIN',
        ], false);

        $this->token = $response['token'];
    }

    public function createPaymentLink(int $amount, string $phone, string $orderId, string $lang = 'en'): string
    {
        $response = $this->request('POST', '/request-to-pay/payment/link', [
            'amount'      => $amount,
            'user_lang'   => $lang,
            'msisdn'      => $phone,
            'success_url' => getenv('APP_URL') . '/payment/success',
            'error_url'   => getenv('APP_URL') . '/payment/error',
        ], true, ['Idempotency-Key: ' . $orderId]);

        return $response['data']['payment_url'];
    }

    public function getTransaction(string $id): array
    {
        return $this->request('GET', "/transactions/$id");
    }

    private function request(string $method, string $endpoint, array $body = [], bool $auth = true, array $extraHeaders = []): array
    {
        $ch = curl_init($this->apiUrl . $endpoint);
        $headers = ['Content-Type: application/json', 'Accept: application/json'];

        if ($auth && $this->token) {
            $headers[] = "Authorization: Bearer {$this->token}";
        }
        $headers = array_merge($headers, $extraHeaders);

        $opts = [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers];
        if ($method === 'POST') {
            $opts[CURLOPT_POST] = true;
            $opts[CURLOPT_POSTFIELDS] = json_encode($body);
        }
        curl_setopt_array($ch, $opts);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode >= 400) {
            handleApiError($response, $httpCode);
        }

        return json_decode($response, true);
    }
}

9Sandbox vs Production

The sandbox lets you test without real charges. Going to production simply means changing the base URL:

CriteriaSandboxProduction
URLapi.elyonpay.net/apiapi.elyonpay.org/api
Real debitsNoYes
Test numbersAvailableN/A

The sandbox provides test phone numbers with simulated behaviors (success, insufficient funds, timeout) and test card numbers for various scenarios. Test thoroughly before going live.

Production checklist: Base URL changed to api.elyonpay.org, production credentials configured, HTTPS on your server, server-side status verification implemented, error handling in place, first test transaction successful with a small amount.

10Security Best Practices

Secure your integration:

  1. Store credentials in environment variables (never in source code)
  2. Never log the JWT token in your application logs
  3. Use HTTPS exclusively โ€” HTTP requests are rejected by the API
  4. Always verify transaction status server-side (don't trust the redirect alone)
  5. Implement the Idempotency-Key header to prevent duplicate payments
  6. Limit payment attempts per session/IP to prevent abuse

Conclusion

You now have all the fundamentals to integrate the ElyonPay API with PHP: JWT authentication, payment link creation, customer redirect, and transaction verification. The API unifies access to all Mobile Money operators (Orange Money, Wave, MTN MoMo, Moov, Airtel) and card payments across 14 African countries. To go further, check the full ElyonPay API documentation which details all available endpoints and options.

Share this article

Get started with ElyonPay

Accept Mobile Money and card payments in minutes.

Create your free account
API