Copied to clipboard!

RwandaPay API Documentation

The RwandaPay API provides a simple, secure, and PCI-compliant way to integrate mobile money payments (MTN MoMo & Airtel Money) into your application. Our API follows industry-standard patterns similar to Stripe, Flutterwave, and Paystack — returning JSON responses with redirect URLs that you control.

✅ Industry Standard API Pattern

Like Stripe, Flutterwave, and Paystack — our API returns JSON only. Your application controls the redirect. After payment, customers are redirected to your configured redirect_url with status parameters.

Lightning Fast

< 200ms response time

PCI Compliant

Secure hosted checkout

99.9% Uptime

Enterprise reliability

24/7 Support

Dedicated assistance

Authentication

All API requests require authentication using your API keys. Include the following headers with every request:

X-Public-KeyYour public API keypk_live_xxxxxxxx or pk_test_xxxxxxxx
X-Secret-KeyYour secret API keysk_live_xxxxxxxx or sk_test_xxxxxxxx
Authentication Headers
X-Public-Key: YOUR_PUBLIC_KEY
X-Secret-Key: YOUR_SECRET_KEY
Content-Type: application/json

🔐 Getting your API keys

Log in to your merchant dashboard → Settings → API Keys to generate your API keys. Test keys (pk_test_*) work in sandbox mode and do NOT process real payments.

Environments

Sandbox / Test Mode

Free

Use pk_test_* keys. Simulates payments without real money.

✨ Test Mode Features:

  • No real money charged
  • No SMS/Email notifications
  • No merchant balance update
  • Instant success response
  • Perfect for integration testing

Live / Production Mode

Real Payments

Use pk_live_* keys. Processes real money transactions.

💰 Live Mode Features:

  • Real money deducted from customers
  • SMS & Email notifications sent
  • Merchant balance updated
  • Webhook notifications sent
  • Real transaction records created

Base URL

Production Base URL
https://pay.rwandapay.rw/api/v1

Payment Flow

The payment flow follows industry-standard patterns (Stripe/Flutterwave):

1
Create Checkout Session
Send a POST request to /checkout/initialize with payment details and your redirect_url.
2
Receive JSON Response
API returns payment_url. Your app redirects customer to this hosted payment page.
3
Customer Payment
Customer enters phone number, selects network (MTN/Airtel), and approves payment on our secure page.
4
Redirect Back
Customer redirected to your redirect_url with ?reference=xxx&status=successful&transaction_id=xxx
5
Webhook Confirmation (Optional)
Real-time webhook sent to your webhook_url for server-side verification.
POST/checkout/initialize

Create Checkout Session

Creates a new payment session and returns a secure payment URL. Your application MUST redirect the customer to this URL to complete payment.

Request Parameters

ParameterTypeRequiredDescription
amountdecimal*Amount in RWF (min: 100, max: 1,000,000)
tx_refstring*Unique transaction reference for your order (max 50 chars)
customer.namestring*Customer's full name
customer.emailemail*Customer's email address (receipt sent here)
customer.phonestring*Customer's phone number (e.g., 0788123456)
currencystringCurrency code (default: RWF, supported: RWF, USD, EUR)
redirect_urlurlURL to redirect customer after payment. If not provided, uses RwandaPay success page.
webhook_urlurlURL for payment confirmation webhook (server-to-server notification)
descriptionstringPayment description (max 500 chars)
metaobjectAdditional metadata for your reference

Response Example (Sandbox Mode)

200 OK - Sandbox Response
{
  "success": true,
  "message": "Checkout session created successfully",
  "data": {
    "reference": "ORDER-1780943716",
    "session_id": "CHK-V0CFMJGNI8ZWIJBR",
    "payment_url": "https://pay.rwandapay.rw/checkout/CHK-V0CFMJGNI8ZWIJBR",
    "status": "pending",
    "amount": 8000,
    "currency": "RWF",
    "mode": "test",
    "expires_at": "2024-12-15T14:30:00Z"
  }
}
200 OK - Live Mode Response
{
  "success": true,
  "message": "Checkout session created successfully",
  "data": {
    "reference": "ORDER-1780943716",
    "session_id": "CHK-V0CFMJGNI8ZWIJBR",
    "payment_url": "https://pay.rwandapay.rw/checkout/CHK-V0CFMJGNI8ZWIJBR",
    "status": "pending",
    "amount": 8000,
    "currency": "RWF",
    "mode": "live",
    "expires_at": "2024-12-15T14:30:00Z"
  }
}

Example Request (JavaScript - Production Ready)

JavaScript Implementation
async function initializePayment(paymentData, apiKeys) {
    // Validate input
    if (!paymentData.amount || paymentData.amount < 100) {
        throw new Error('Amount must be at least 100 RWF');
    }
    if (!paymentData.customer?.phone) {
        throw new Error('Customer phone number is required');
    }
    
    // Disable button and show loading
    const payButton = document.getElementById('pay-button');
    if (payButton) {
        payButton.disabled = true;
        payButton.innerHTML = '<div class="spinner"></div> Processing...';
    }
    
    try {
        const response = await fetch('https://pay.rwandapay.rw/api/v1/checkout/initialize', {
            method: 'POST',
            headers: {
                'X-Public-Key': apiKeys.publicKey,
                'X-Secret-Key': apiKeys.secretKey,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                amount: paymentData.amount,
                tx_ref: paymentData.tx_ref || 'ORDER-' + Date.now(),
                currency: paymentData.currency || 'RWF',
                customer: {
                    name: paymentData.customer.name,
                    email: paymentData.customer.email,
                    phone: paymentData.customer.phone
                },
                redirect_url: paymentData.redirect_url || window.location.href + '/callback',
                webhook_url: paymentData.webhook_url,
                description: paymentData.description,
                meta: paymentData.meta || {}
            })
        });
        
        const result = await response.json();
        
        if (result.success) {
            // Redirect to hosted payment page
            window.location.href = result.data.payment_url;
            return result.data;
        } else {
            throw new Error(result.message || 'Payment initialization failed');
        }
    } catch (error) {
        console.error('Payment error:', error);
        showErrorToast(error.message);
        return null;
    } finally {
        if (payButton) {
            payButton.disabled = false;
            payButton.innerHTML = 'Pay Now';
        }
    }
}

// Usage example
initializePayment({
    amount: 5000,
    tx_ref: 'ORDER-' + Date.now(),
    customer: {
        name: 'John Doe',
        email: 'john@example.com',
        phone: '0788123456'
    },
    redirect_url: 'https://yoursite.com/payment-callback',
    webhook_url: 'https://yoursite.com/api/webhook',
    description: 'Payment for Order #12345'
}, {
    publicKey: 'YOUR_PUBLIC_KEY',
    secretKey: 'YOUR_SECRET_KEY'
});

🔑 Important: Payment URL

The payment_url contains the unique session ID. Use the reference from the response for polling status.

GET/checkout/{reference}/verify

Verify Payment Status

Check payment status using the reference from the checkout response. Poll this endpoint every 3-5 seconds until payment is confirmed.

Pending Response
{
  "status": "pending",
  "completed": false,
  "success": false,
  "message": "Waiting for payment confirmation...",
  "elapsed_seconds": 15,
  "mode": "live"
}
Successful Response (with redirect URL)
{
  "status": "successful",
  "completed": true,
  "success": true,
  "message": "Payment successful!",
  "redirect_url": "https://yoursite.com/callback?reference=ORDER-123&status=successful&transaction_id=PAY-XXX",
  "amount": 5000,
  "mode": "live"
}
Test Mode (Sandbox) Response
{
  "status": "successful",
  "completed": true,
  "success": true,
  "message": "Test payment successful!",
  "redirect_url": "https://yoursite.com/callback?reference=ORDER-123&status=successful",
  "amount": 5000,
  "mode": "test"
}
Failed Response
{
  "status": "failed",
  "completed": true,
  "success": false,
  "message": "Payment failed",
  "redirect_url": "https://yoursite.com/callback?reference=ORDER-123&status=failed"
}

Polling Implementation Example

JavaScript Polling
function pollPaymentStatus(reference, redirectUrl) {
    let attempts = 0;
    const maxAttempts = 60; // 60 * 3 seconds = 3 minutes max
    let interval;
    
    function checkStatus() {
        attempts++;
        console.log(`Checking payment status (attempt ${attempts}/${maxAttempts})...`);
        
        fetch(`https://pay.rwandapay.rw/api/v1/checkout/${reference}/verify`, {
            headers: {
                'Accept': 'application/json'
            }
        })
        .then(response => response.json())
        .then(data => {
            if (data.completed === true) {
                clearInterval(interval);
                if (data.success === true) {
                    // Redirect to success URL
                    window.location.href = data.redirect_url || redirectUrl;
                } else {
                    // Show error and redirect
                    showError(data.message);
                    setTimeout(() => {
                        window.location.href = data.redirect_url || '/';
                    }, 3000);
                }
            } else if (attempts >= maxAttempts) {
                clearInterval(interval);
                showError('Payment verification timeout. Please check your email.');
            }
        })
        .catch(error => {
            console.error('Polling error:', error);
            if (attempts >= maxAttempts) {
                clearInterval(interval);
                showError('Unable to verify payment status.');
            }
        });
    }
    
    // Start polling every 3 seconds
    interval = setInterval(checkStatus, 3000);
    checkStatus(); // Check immediately
}
POST(Your configured webhook URL)

Payment Webhook

RwandaPay sends real-time payment status updates to your configured webhook URL. Always verify the payment on your server-side using webhooks — do not rely solely on client-side redirects.

Webhook Payload (Successful Payment)
{
  "event": "payment.successful",
  "data": {
    "reference": "ORDER-12345",
    "amount": 5000,
    "currency": "RWF",
    "status": "successful",
    "customer": {
      "name": "John Doe",
      "email": "john@example.com",
      "phone": "0788123456"
    },
    "paid_at": "2024-12-15T14:30:00Z",
    "transaction_id": "PAY-LIVE-XXXXXXXX-20241215143000",
    "paypack_reference": "550e8400-e29b-41d4-a716-446655440000",
    "mode": "live"
  },
  "timestamp": "2024-12-15T14:30:05Z"
}

Webhook Verification (PHP Example)

PHP Webhook Handler
<?php
// Your webhook endpoint (e.g., https://yoursite.com/api/webhook)
header('Content-Type: application/json');

$payload = json_decode(file_get_contents('php://input'), true);

// Verify webhook signature (implement your signature verification)
if (!verifyWebhookSignature($payload)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit;
}

if ($payload['event'] === 'payment.successful') {
    $data = $payload['data'];
    
    // Update your database
    $orderId = $data['reference'];
    $amount = $data['amount'];
    $transactionId = $data['transaction_id'];
    
    // Mark order as paid
    $updated = DB::table('orders')
        ->where('reference', $orderId)
        ->where('status', 'pending')
        ->update([
            'status' => 'paid',
            'transaction_id' => $transactionId,
            'paid_at' => now(),
            'updated_at' => now()
        ]);
    
    if ($updated) {
        // Send confirmation email to customer
        // Update inventory
        // Trigger any business logic
        Log::info("Payment confirmed for order: {$orderId}");
    }
}

// Always return 200 OK to acknowledge receipt
http_response_code(200);
echo json_encode(['status' => 'ok']);

Request Structure

Full Request Example
{
  "amount": 5000,
  "tx_ref": "ORDER-12345",
  "currency": "RWF",
  "customer": {
    "name": "John Doe",
    "email": "john@example.com",
    "phone": "0788123456"
  },
  "redirect_url": "https://yoursite.com/payment-callback",
  "webhook_url": "https://yoursite.com/api/webhook",
  "description": "Payment for Order #12345",
  "meta": {
    "cart_id": "cart_123",
    "user_id": 456
  }
}

Response Codes

HTTP CodeMeaningWhen it happens
200 OKSuccessRequest processed successfully
201 CreatedCreatedCheckout session created
401 UnauthorizedAuth FailedInvalid or missing API keys
409 ConflictDuplicatetx_ref already exists
422 UnprocessableValidation ErrorMissing or invalid required fields
500 Server ErrorInternal ErrorContact support if persists

Official SDKs

Accelerate your integration with our official SDKs:

PHP SDK

composer require rwandapay/php-sdk

JavaScript SDK

npm install rwandapay-js

Python SDK

pip install rwandapay

Java SDK

Maven: com.rwandapay

Error Codes

Error CodeHTTP StatusDescriptionResolution
AUTH_FAILED401Invalid API credentialsVerify your API keys
DUPLICATE_REFERENCE409tx_ref already usedUse a unique reference
INITIALIZATION_FAILED500Could not create sessionContact support
SESSION_NOT_FOUND404Checkout session expiredCreate new session
INSUFFICIENT_BALANCE422Customer has insufficient fundsAsk customer to retry

Rate Limits

Rate LimitBurst LimitReset Time
500 requests/minute1000 requests/minuteRolling window
10,000 requests/day-Resets at 00:00 UTC

When rate limit is exceeded, the API returns 429 Too Many Requests. Implement exponential backoff in your integration.

Need Help?

Our developer support team is here to help you integrate.