Maly Maly Payments v1 Changelog Partners
Base URL Applied to every example. Stored only in this browser.
API reference

Payments

Collect money from a customer's mobile money wallet.

POST{BASE_URL}/payments

Initiate a payment. Returns immediately with a non-final status.

Request body#

Request
{
  "method": "mobile_money_mtn",
  "country": "CG",
  "amount": { "value": "11800", "currency": "XAF" },
  "reference": "ORDER-123",
  "idempotency_key": "a4f3c2d1-0000-0000-0000-000000000000",
  "customer": { "phone": "242064661331" },
  "description": "Payment for order ORDER-123",
  "charge": {
    "items": [
      { "name": "Product name", "quantity": 2, "unit_price": "5000", "total": "10000" }
    ],
    "subtotal": "10000"
  },
  "tax": { "inclusive": false, "amount": "1800", "rate": "18.00", "type": "VAT" },
  "metadata": { "order_id": "ORDER-123" }
}
FieldRequiredDescription
methodYesPayment method code (from /payment-methods)
countryFor country-scoped methodsISO 3166-1 alpha-2 country code
amount.valueYesTotal amount to charge, as a minor-units string
amount.currencyYesISO-4217 currency code
idempotency_keyYesA unique UUID for this request
customerYesCustomer details. The required properties are set by the selected method's required_customer_fields. For mobile money this is phone
referenceNoYour own order or transaction reference
descriptionNoShort description (may be shown to the customer)
chargeNoLine items and subtotal, for receipts and reporting
taxNoTax breakdown
metadataNoAny key/value pairs, stored and echoed back on webhooks

Tax#

amount.value is always the total you are charging the customer. tax.inclusive describes how that total was arrived at.

tax.inclusiveMeaning
trueTax is already contained within amount.value.
falseamount.value is the subtotal plus tax. In this case charge.subtotal + tax.amount must equal amount.value.

In the example above, the subtotal is 10000, tax is 1800, and amount.value is 11800. The customer is charged 11800.

Customer phone number#

Send the phone number as digits only: the country code followed by the national number, with no plus sign, no spaces and no separators. For Republic of Congo this is 12 digits, the 242 country code followed by the 9-digit national number, keeping its leading zero.

i

Examples are illustrative. The phone numbers, identifiers and references shown throughout this documentation are sample values for illustration. Use your own customer and order data in live calls.

ExampleValid?
242064661331Valid
+242064661331No plus sign
064661331Country code missing
242 064 661 331No spaces

Tax#

When tax.inclusive is true, charge.subtotal equals amount.value and tax.amount is the portion already inside that total. When tax is sent with inclusive: false, charge.subtotal is required, because the rule charge.subtotal + tax.amount = amount.value depends on it. Send neither tax nor charge and no tax validation applies.

i

Tax is yours to determine. The gateway records the tax values you send. It does not calculate tax or determine the applicable treatment, and nothing here is tax advice.

Line-item arithmetic#

When you send charge, the figures must be internally consistent. For each item, quantity multiplied by unit_price must equal item.total, and the item totals must sum to charge.subtotal. In the example, 2 at 5000 is 10000, which is the subtotal.

Examples#

Create a payment
curl -X POST "{BASE_URL}/payments" \
  -H "X-Api-Key: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "mobile_money_mtn",
    "country": "CG",
    "amount": { "value": "11800", "currency": "XAF" },
    "reference": "ORDER-123",
    "idempotency_key": "a4f3c2d1-0000-0000-0000-000000000000",
    "customer": { "phone": "242064661331" }
  }' 
const res = await fetch("{BASE_URL}/payments", {
  method: "POST",
  headers: {
    "X-Api-Key": process.env.MALY_API_KEY,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    method: "mobile_money_mtn",
    country: "CG",
    amount: { value: "11800", currency: "XAF" },
    reference: "ORDER-123",
    idempotency_key: crypto.randomUUID(),
    customer: { phone: "242064661331" }
  })
});

const payment = await res.json();
console.log(payment.id, payment.status);
import os, uuid, requests

res = requests.post(
    "{BASE_URL}/payments",
    headers={
        "X-Api-Key": os.environ["MALY_API_KEY"],
        "Content-Type": "application/json",
    },
    json={
        "method": "mobile_money_mtn",
        "country": "CG",
        "amount": {"value": "11800", "currency": "XAF"},
        "reference": "ORDER-123",
        "idempotency_key": str(uuid.uuid4()),
        "customer": {"phone": "242064661331"},
    },
)

payment = res.json()
print(payment["id"], payment["status"])
<?php
$ch = curl_init("{BASE_URL}/payments");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        "X-Api-Key: " . getenv("MALY_API_KEY"),
        "Content-Type: application/json",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "method" => "mobile_money_mtn",
        "country" => "CG",
        "amount" => ["value" => "11800", "currency" => "XAF"],
        "reference" => "ORDER-123",
        "idempotency_key" => bin2hex(random_bytes(16)),
        "customer" => ["phone" => "242064661331"],
    ]),
]);

$payment = json_decode(curl_exec($ch), true);
echo $payment["id"], " ", $payment["status"];

Response#

Response · 202 Accepted
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "pending",
  "method": "mobile_money_mtn",
  "country": "CG",
  "amount": { "value": "11800", "currency": "XAF" },
  "reference": "ORDER-123",
  "idempotency_key": "a4f3c2d1-0000-0000-0000-000000000000",
  "provider_reference": null,
  "failure_reason": null,
  "created_at": "2026-06-27T09:00:00Z",
  "updated_at": "2026-06-27T09:00:00Z"
}

Get a payment's status#

GET{BASE_URL}/payments/{id}

Returns the same object shape, with the current status and, once final, a failure_reason if applicable.

Request
curl "{BASE_URL}/payments/550e8400-e29b-41d4-a716-446655440000" \
  -H "X-Api-Key: <your-api-key>"

Payment statuses#

StatusMeaning
pendingAwaiting customer approval
processingAccepted by the provider and being processed
successFunds received; your wallet has been credited
failedRefused by the customer or provider
expiredCustomer did not respond in time

List payments#

GET{BASE_URL}/payments

Filterable list of your payments.

Request
curl "{BASE_URL}/payments?status=success&method=mobile_money_mtn&limit=50&offset=0" \
  -H "X-Api-Key: <your-api-key>"

Optional filters: status, method, from_date, to_date, limit (max 200), offset.

List response#

List endpoints return a page wrapper, not a bare array. The same shape is used by payments, payouts and transactions.

Response · 200 OK
{
  "items": [ ... ],
  "total": 143,
  "limit": 50,
  "offset": 0
}
FieldMeaning
itemsThe results for this page
totalCount of all matching records, before pagination
limitPage size applied (max 200)
offsetStart position of this page

To page, increase offset by limit until offset plus the number of returned items reaches total. A query with no matches returns an empty items array with total of 0, not a 404.

Date filters#

from_date and to_date take ISO 8601 timestamps in UTC. Bounds are inclusive. There is no maximum query window.

Maly Payments API v1 · Documentation 1.1 · July 2026
Maly Tech Ltd. This guide is provided for information. Where it differs from your signed agreement, the agreement applies.