MyroPay API Reference
The MyroPay API is a RESTful HTTP API. It uses JSON request bodies, returns JSON responses, and uses standard HTTP status codes. All endpoints require HTTPS.
Base URLs
# Checkout API (SDK-facing — auth via checkout key)
https://checkout.myropay.com/api/
# Business API (auth via JWT or business key)
https://business.myropay.com/api/
# Personal App API (auth via JWT)
https://app.myropay.com/api/
ck_test_* or sk_test_* keys to test against our sandbox. No real money moves. Behaviour is identical to live.
Authentication
MyroPay supports two authentication methods:
1. Checkout app key (server-side)
Pass your checkout secret key in the X-Checkout-Key header for all Checkout API calls.
X-Checkout-Key: ck_sec_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
2. JWT Bearer token (dashboard / business API)
Obtain a JWT via POST /api/auth.php?action=login then pass in the Authorization header:
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOjEsImFjdG9yX3R5cGUiOiJidXNpbmVzcyJ9.xxx
ck_sec_*, sk_live_*) in client-side code, browser JavaScript, or mobile apps. Use publishable keys (ck_pub_*) on the client side only.
Key types
| Key prefix | Type | Use case | Expiry |
|---|---|---|---|
| ck_pub_ | Publishable test | Client-side JS only | Never |
| ck_sec_ | Secret test | Server-side only | Never (rotatable) |
| pk_live_ | Publishable live | Client-side (KYB required) | Never |
| sk_live_ | Secret live | Server-side only (KYB required) | Never (rotatable) |
| ck_wh_ | Webhook secret | HMAC-SHA256 signature verification | Never (rotatable) |
Errors & responses
All API responses share a consistent JSON envelope:
{
"success": true,
"message": "Session created",
"data": { /* response payload */ }
}
{
"success": false,
"message": "amount must be greater than 0",
"error_code": "VALIDATION_ERROR",
"status_code": 422
}
| HTTP code | Meaning | Retry? |
|---|---|---|
| 200 | Success | — |
| 201 | Created | — |
| 400 | Bad request / unknown action | No |
| 401 | Authentication failed | No |
| 403 | Forbidden (KYB/KYC required) | No |
| 404 | Resource not found | No |
| 409 | Conflict (duplicate) | No |
| 410 | Gone (session expired) | No |
| 422 | Validation failed | No (fix params) |
| 429 | Rate limit exceeded | Yes (after retry-after) |
| 500 | Internal server error | Yes (with backoff) |
| 502 | Upstream gateway error | Yes (with backoff) |
Pagination
List endpoints return paginated results. Pass page and per_page as query parameters:
GET /api/invoices.php?action=list&page=2&per_page=20
{
"success": true,
"data": {
"data": [ /* array of items */ ],
"total": 143,
"page": 2,
"per_page": 20,
"total_pages": 8,
"has_next": true,
"has_prev": true
}
}
Rate limits
| Endpoint group | Default limit | Window |
|---|---|---|
| Authentication | 10 requests | 10 minutes |
| Checkout session create | 100 requests | 1 minute |
| Transfers / withdrawals | 20 requests | 1 minute |
| General API | 300 requests | 1 minute |
| Analytics endpoints | 60 requests | 1 minute |
Rate limit headers returned on every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset. On 429, check the Retry-After header.
Idempotency
For POST requests that create resources or execute transactions, include an idempotency_key in the request body. If the same key is submitted within 24 hours, the original response is returned without re-executing. Prevents duplicate charges from network retries.
{
"amount": 5000,
"currency_code": "NGN",
"idempotency_key": "order_9876_attempt_1"
}
Webhooks
Configure a webhook URL in your checkout app settings. MyroPay sends signed POST requests to your URL when events occur. Always verify the X-MyroPay-Signature header before processing.
import MyroPay from 'myropay-js';
app.post('/webhook', express.raw({type:'application/json'}), (req, res) => {
const sig = req.headers['x-myropay-signature'];
const secret = 'ck_wh_your_secret';
if (!await MyroPay.verifyWebhook(req.body, sig, secret)) {
return res.status(401).send('Unauthorized');
}
const event = JSON.parse(req.body);
switch(event.event) {
case 'checkout.completed': fulfillOrder(event.data); break;
}
res.json({ received: true });
});
$payload = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_MYROPAY_SIGNATURE'] ?? '';
$secret = 'ck_wh_your_secret';
$expected= hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $sig)) {
http_response_code(401); exit;
}
$event = json_decode($payload, true);
if ($event['event'] === 'checkout.completed') {
fulfillOrder($event['data']);
}
import hmac, hashlib
@app.route('/webhook', methods=['POST'])
def webhook():
payload = request.get_data()
sig = request.headers.get('X-MyroPay-Signature', '')
secret = b'ck_wh_your_secret'
expected = hmac.new(secret, payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig):
return 'Unauthorized', 401
event = request.get_json()
if event['event'] == 'checkout.completed':
fulfill_order(event['data'])
return '', 200
SDKs
npm install myropay-jscomposer require myropay/php-sdkpip install myropaynpm install @myropay/reactCheckout — Create session
Creates a new checkout session. Returns a checkout_url to redirect your customer to. Sessions expire after 30 minutes.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| amount | number | required | Amount to charge. In the smallest unit (kobo for NGN, cents for USD, pence for GBP) |
| currency_code | string | required | ISO 4217 code: NGN, USD, GBP, EUR, GHS, KES… |
| description | string | optional | Shown on the checkout page to the customer |
| customer_email | string | optional | Pre-fills email on the checkout form |
| customer_name | string | optional | Pre-fills name on the checkout form |
| success_url | string | optional | Redirect URL after successful payment (overrides app default) |
| cancel_url | string | optional | Redirect URL if customer cancels |
| metadata | object | optional | Key-value pairs returned in the webhook payload. Max 50 keys, 500 chars per value. |
| idempotency_key | string | optional | Prevents duplicate sessions on network retries |
import MyroPay from 'myropay-js';
const client = new MyroPay('ck_sec_live_xxxx');
const session = await client.checkout.create({
amount: 500000, // ₦5,000.00 in kobo
currency_code: 'NGN',
description: 'Order #1234',
customer_email: 'customer@example.com',
success_url: 'https://mystore.com/success',
cancel_url: 'https://mystore.com/cancel',
metadata: { order_id: 'ORD-9876' },
idempotency_key: 'order-9876-attempt-1',
});
// Redirect customer to payment page
window.location.href = session.checkout_url;
use MyroPay\Client;
$client = new Client('ck_sec_live_xxxx');
$session = $client->checkout->create([
'amount' => 500000,
'currency_code' => 'NGN',
'description' => 'Order #1234',
'customer_email' => 'customer@example.com',
'success_url' => 'https://mystore.com/success',
'cancel_url' => 'https://mystore.com/cancel',
'metadata' => ['order_id' => 'ORD-9876'],
'idempotency_key' => 'order-9876-attempt-1',
]);
header('Location: ' . $session['checkout_url']);
import myropay
client = myropay.Client("ck_sec_live_xxxx")
session = client.checkout.create(
amount=500000,
currency_code="NGN",
description="Order #1234",
customer_email="customer@example.com",
success_url="https://mystore.com/success",
cancel_url="https://mystore.com/cancel",
metadata={"order_id": "ORD-9876"},
idempotency_key="order-9876-attempt-1",
)
return redirect(session["checkout_url"])
curl -X POST https://checkout.myropay.com/api/session.php?action=create \
-H "X-Checkout-Key: ck_sec_live_xxxx" \
-H "Content-Type: application/json" \
-d '{
"amount": 500000,
"currency_code": "NGN",
"description": "Order #1234",
"customer_email": "customer@example.com",
"success_url": "https://mystore.com/success",
"cancel_url": "https://mystore.com/cancel",
"metadata": { "order_id": "ORD-9876" },
"idempotency_key": "order-9876-attempt-1"
}'
{
"success": true,
"data": {
"session_id": 42,
"session_token": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"checkout_url": "https://checkout.myropay.com/pay/a1b2c3d4e5f6g7h8i9j0",
"expires_at": "2025-01-15 13:04:56",
"amount": 500000,
"currency": "NGN"
}
}
Retrieves the current status of a checkout session by its token.
| Parameter | Type | Description |
|---|---|---|
| token | string | The session_token returned by create |
Immediately expires a pending session (e.g. if the customer cancels in your own UI before redirecting).
{ "token": "a1b2c3d4..." }Invoices
Returns paginated list of invoices for the authenticated business.
| Query param | Type | Description |
|---|---|---|
| page | integer | Page number (default: 1) |
| per_page | integer | Results per page (default: 20, max: 50) |
| status | string | Filter by status: draft | sent | paid | overdue | void |
| search | string | Full-text search on invoice number, client name, email |
Creates a new invoice. Returns the invoice ID and a public payment link.
| Parameter | Type | Required | Description |
|---|---|---|---|
| client_name | string | required | Client display name |
| client_email | string | required | Client email (where invoice email is sent) |
| items | array | required | Array of line item objects (see below) |
| items[].description | string | required | Line item description |
| items[].quantity | number | required | Quantity |
| items[].unit_price | number | required | Price per unit |
| items[].tax_rate | number | optional | Tax percentage (e.g. 7.5 for 7.5%) |
| currency_code | string | optional | Default: business wallet currency |
| due_date | string | optional | Due date in YYYY-MM-DD format |
| notes | string | optional | Payment terms / notes shown on invoice |
| discount_percent | number | optional | Whole-invoice discount (0–100) |
| allow_partial | boolean | optional | Allow partial online payments (default: false) |
Returns full invoice detail including line items and payment history.
Sends the invoice to the client's email. Transitions status from draft to sent.
{ "id": 42 }Voids an invoice. Cannot be undone. Cannot void a paid invoice.
Wallets
Returns all active wallets for the authenticated business, ordered by primary first.
{
"wallets": [{
"id": 1,
"currency_code": "NGN",
"currency_symbol": "₦",
"balance": "125000.00",
"locked_balance": "0.00",
"is_primary": 1,
"flw_account_number":"1234567890",
"flw_bank_name": "Wema Bank"
}]
}
Looks up a user or business by Myrotag or email. Use before initiating a transfer.
Returns the virtual bank account for the primary wallet. Creates one if it doesn't exist yet.
FX Rates
Returns live exchange rates for a base currency. Rates update every 30 minutes from our liquidity providers.
Preview a currency conversion with fee breakdown. Does not execute the conversion.
| Parameter | Type | Description |
|---|---|---|
| from | string | Source currency code (e.g. USD) |
| to | string | Target currency code (e.g. NGN) |
| amount | number | Amount to convert in source currency |
Analytics
Returns KPI summary for the specified time period.
| Query param | Values | Description |
|---|---|---|
| period | 7d 30d 90d 12m custom | Time window. Use custom with from and to params. |
| from | string | Start date YYYY-MM-DD (required when period=custom) |
| to | string | End date YYYY-MM-DD (required when period=custom) |
Returns time-series revenue data for charting.
| Query param | Values | Description |
|---|---|---|
| granularity | hour day week month | Data point interval |
Webhook events
checkout.completed
{
"event": "checkout.completed",
"app_id": 7,
"timestamp":1714123456,
"data": {
"session": {
"id": 42,
"session_token": "a1b2c3d4...",
"amount": 500000,
"currency_code": "NGN",
"customer_email":"customer@example.com",
"customer_name": "John Doe",
"status": "completed",
"paid_at": "2025-01-15 12:34:56",
"metadata": { "order_id": "ORD-9876" }
}
}
}transfer.completed
{
"event": "transfer.completed",
"data": {
"reference": "MPY-20250115-ABCD1234",
"amount": 10000,
"currency": "NGN",
"status": "SUCCESSFUL",
"bank_name": "First Bank",
"account": "****1234"
}
}invoice.paid
{
"event": "invoice.paid",
"data": {
"invoice_number": "INV-2025-0042",
"client_email": "client@company.com",
"amount": 250000,
"currency_code": "NGN",
"paid_at": "2025-01-15 14:20:00"
}
}payroll.completed
{
"event": "payroll.completed",
"data": {
"batch_id": 12,
"batch_name": "January 2025 Salary",
"recipient_count": 24,
"total_amount": "4800000.00",
"currency_code": "NGN",
"completed_at": "2025-01-31 14:00:00"
}
}Live playground
Test API calls directly. Use your test secret key — no real transactions.