Skip to content

PayAlo Merchant API V2 Reference


Table of Contents

  1. Overview
  2. Authentication & Security
  3. Endpoints
  4. Shared Data Models
  5. Error Handling
  6. Transaction Lifecycle
  7. Callback (Webhook) Documentation
  8. Limitations & Known Constraints
  9. Production Readiness Checklist

1. Overview

API Purpose

The PayAlo Merchant API V2 is a REST interface for mobile money operations (MMO) that enables merchants to:

  • Initiate pay-in transactions — direct (STK push), QR-based, or web redirect (Hosted Payment Page).
  • Initiate pay-out transactions — direct disbursements to mobile money accounts.
  • Initiate tax payouts — payments to tax authorities.
  • Look up transaction status and reconcile records — fetch a single transaction by reference, or page through transactions in a date window.

All endpoints follow an asynchronous, fire-and-forget model: the gateway validates the request synchronously and returns immediately, while payment processing happens in the background. Results are delivered via webhook callbacks.

New here? Start with the Quickstart — the smallest end-to-end path through a transaction.

Sample integrations: Runnable reference apps in .NET (Blazor), Node.js (Express + TypeScript), and Python (FastAPI) — each one exercises every endpoint in this reference. See Payalo-Git/payalo-docs-examples.

Base URLs

Environment Base URL
Production https://api.payalo.com/gateway/mmo/v2

Note: Some brands are routed under a custom host. If your account manager has provisioned a brand-specific URL, use that — otherwise use the URL above.

Authentication Method

All requests must include an API key in the X-Api-Key HTTP header. See Authentication & Security for full details.

General Request / Response Format

Aspect Convention
Content type (request) application/json
Content type (error response) application/problem+json
Enum serialization Strings (e.g., "pending")
Timestamps ISO 8601 with UTC offset (e.g., "2024-06-01T12:34:56+00:00")
Amounts Decimal numbers inside a Money object (e.g., { "value": 10.50, "currency": "EUR" })
Currency codes ISO 4217 (e.g., "EUR", "USD", "KES")
Country codes ISO 3166-1 alpha-2 (e.g., "DE", "KE", "US")
Error format RFC 7807 Problem Details (see Error Handling)

Payment Method as Route Parameter

In V2, the payment method is specified as a path parameter rather than a request body field:

POST /gateway/mmo/v2/{flow}/{direction}/{method}

For example, to initiate a direct pay-in via M-Pesa in Kenya:

POST /gateway/mmo/v2/direct/payin/mpesa-ke

The {method} parameter corresponds to the payment method key configured for your brand (e.g., mpesa-ke, airtel-ug, mtn-gh).


2. Authentication & Security

API Key Usage

Authentication uses a static API key issued per brand (merchant account).

Header:

X-Api-Key: <your-api-key>
Scenario HTTP Response
X-Api-Key header missing 401 Unauthorized
API key invalid or unknown 401 Unauthorized
API key belongs to a disabled brand 400 Bad Request with errorCode: "validation_failed" and type ending in merchant_disabled (see merchant_disabled)

API keys are validated on every request. Valid keys are cached server-side for up to 1 minute to reduce latency.

Contact your account manager to obtain, rotate, or revoke API keys.

Required Headers

Header Required Description
X-Api-Key Yes Your brand API key
Content-Type Yes Must be application/json

Idempotency Rules

Every transaction creation request requires a merchantReference field that acts as an idempotency key.

Rule Detail
Scope Unique per brand (merchant account)
TTL The gateway tracks submitted IDs for 15 minutes in its primary deduplication store. The downstream orchestrator may reject duplicates for longer (e.g. settled-transaction reuse).
Duplicate behavior Returns 422 Unprocessable Entity with error code merchant_transactionid_duplicate
Reuse after TTL After the deduplication window expires, the same merchantReference may technically be accepted, but reuse is strongly discouraged
Failed transactions A merchantReference is consumed on submission — it cannot be reused even if the original transaction fails

Best practice: Use a UUID or your internal order ID as the merchantReference. Never retry a failed transaction with the same ID; generate a new one instead.


3. Endpoints

Endpoint Route Description
Direct Pay-In POST /gateway/mmo/v2/direct/payin/{method} Server-to-server deposit (e.g., mobile money STK push)
Direct Pay-Out POST /gateway/mmo/v2/direct/payout/{method} Server-to-server withdrawal / disbursement
QR Pay-In POST /gateway/mmo/v2/qr/payin/{method} QR code-based deposit
Web Pay-In (HPP) POST /gateway/mmo/v2/web/payin/{method} Redirect to Hosted Payment Page
Direct Tax Payout POST /gateway/mmo/v2/direct/taxpayout/{method} Tax authority payout
Payment Status GET /gateway/mmo/v2/status/{gatewayReference} Get transaction status by gateway reference
Payment Status GET /gateway/mmo/v2/status/mref/{merchantReference} Get transaction status by merchant reference
Records GET /gateway/mmo/v2/records List transactions in a creation-date window for bulk reconciliation

4. Shared Data Models

The following data models are shared across multiple endpoints.

Money

Represents a monetary amount with its currency.

Field Type Required Description
value decimal Yes Amount value. Must be greater than 0. Decimal precision must match the currency (e.g., 2 decimals for EUR/KES, 0 for JPY).
currency string Yes ISO 4217 currency code (e.g., "KES", "EUR", "USD"). Must be supported for the requested payment method.
{
  "value": 500.00,
  "currency": "KES"
}

Msisdn Party

Represents an end user (payer or payee) identified by phone number.

Field Type Required Max Length Description
id string Yes 255 Your identifier for the end user
msisdn string Yes 20 Phone number in international format (e.g., "+254712345678"). Minimum 3 characters.
firstName string No 255 End user's first name
lastName string No 255 End user's last name
email string No 320 End user's email address (must be a valid email format)
{
  "id": "user-42",
  "msisdn": "+254712345678",
  "firstName": "Jane",
  "lastName": "Doe",
  "email": "[email protected]"
}

Note: The party object is named payer for pay-in endpoints and payee for pay-out endpoints. The Tax Payout endpoint does not require a party object.

Labels

An optional key-value dictionary for attaching custom metadata to a transaction. Maximum 10 entries.

{
  "labels": {
    "orderId": "ORD-2024-001",
    "channel": "mobile-app"
  }
}

Initiate Payment Response

The standard success response for all payment initiation endpoints (except Web Pay-In, which extends this with additional fields).

Field Type Description
status string Initial transaction status. Always "pending".
gatewayReference string Unique transaction identifier assigned by the gateway (ULID format)
merchantReference string Echo of the merchantReference from your request
reconciliationReference string The reconciliationReference from your request, or the merchantReference if not provided
createdAt string Timestamp when the transaction was created (ISO 8601 with UTC offset)
{
  "status": "pending",
  "gatewayReference": "b2p01j3abcdef0000000000000000a1b2",
  "merchantReference": "dep-20240601-001",
  "reconciliationReference": "INV-2024-001",
  "createdAt": "2024-06-01T12:34:56+00:00"
}

Web Pay In Response

Extended success response returned only by the Web Pay-In endpoint. Includes all fields from Initiate Payment Response plus:

Field Type Description
pageUrl string URL of the Hosted Payment Page. Redirect the end user's browser to this URL to complete the payment.
pageOpenMode string How to open the payment page. Currently always "redirect".
{
  "status": "pending",
  "gatewayReference": "b2p01j3ghjkmn0000000000000000c3d4",
  "merchantReference": "dep-web-20240601-001",
  "reconciliationReference": "dep-web-20240601-001",
  "createdAt": "2024-06-01T12:34:56+00:00",
  "pageUrl": "https://hpp.payalo.com/pay/session-xyz789",
  "pageOpenMode": "redirect"
}

Mmo Transaction

The full transaction representation returned by the Payment Status endpoints and delivered as the body of Callbacks — both use the same shape.

Field Type Description
status string "pending", "success", or "failed"
type string "payin", "payout", or "tax"
flow string "direct", "web", "qr", or "push"
gatewayReference string Unique transaction ID (ULID format)
merchantReference string | null Your idempotency key. Always present for merchant-initiated flows (direct, web, qr). Always null for push transactions, which are provider-initiated and have no merchant request.
reconciliationReference string | null Your reconciliation reference. For merchant-initiated flows, this is the value you submitted, or the merchantReference if you omitted it. Always null for push transactions.
providerReference string | null Payment provider's own transaction reference
party Msisdn Party Payer or payee details
method string Payment method key used
country string ISO 3166-1 alpha-2 country code
requestedAmount Money Amount originally requested
finalAmount Money | null Final settled amount, null if not yet settled
labels object | null Key-value metadata submitted with the transaction. Always null for push transactions (no merchant request to attach metadata to).
createdAt string Creation timestamp (ISO 8601 UTC)
completedAt string | null Completion timestamp (ISO 8601 UTC), null if still pending
completionSource string | null What resolved the transaction. Known values: "webhook" (provider callback), "poll" (gateway polled the provider), "manual" (operator override). null if still pending. Treat unknown values as informational.
errorCode string | null Machine-readable error code if the transaction failed
errorMessage string | null Human-readable description of the error
providerData Provider Data | null Provider-specific details, null if not yet routed

See Payment Status for full examples.

Provider Data

Provider-specific details included in the Mmo Transaction once a transaction has been routed to a provider.

Field Type Description
name string Provider identifier (e.g., "mpesa")
title string Provider display name (e.g., "M-Pesa Kenya")
fee Money | null Transaction fee charged by the provider
partyData object | null Additional party details returned by the provider (e.g., {"firstName": "Jane"})
errorCode string | null Provider's native error code
errorMessage string | null Provider's native error message

5. Error Handling

Error Response Structure

All API errors follow the RFC 7807 Problem Details format with an application/problem+json content type:

{
  "type": "https://docs.payalo.com/errors/validation_failed",
  "title": "Validation failed",
  "status": 400,
  "detail": "Human-readable description of the error.",
  "errorCode": "validation_failed"
}
Field Type Description
type string URI reference identifying the specific error type. Final path segment is the specific cause (e.g. config_unsupported_currency).
title string Short error category.
status integer HTTP status code.
detail string Human-readable explanation of this specific occurrence.
errorCode string Generic error category for programmatic handling. For 400 validation failures this is always "validation_failed"; for 422 business-logic errors with a dedicated code this matches the final segment of type. See How to read these errors.

HTTP Status to Title Mapping

HTTP Status title errorCode Description
400 "Validation failed" validation_failed A field failed validation rules. The type URL carries the specific cause when one is defined (e.g. merchant_disabled, config_unsupported_currency).
400 "Bad request" bad_request Request body is malformed or unreadable.
401 "Unauthorized" unauthorized API key is missing or invalid.
404 "Not found" not_found Requested resource does not exist.
422 "Business logic error" business_logic_error, or a specific code such as merchant_transactionid_duplicate Business rule violation.
500 "Internal server error" internal_server_error Unexpected server-side error.

Example Error Responses

Validation error (missing required field):

{
  "type": "https://docs.payalo.com/errors/validation_failed",
  "title": "Validation failed",
  "status": 400,
  "detail": "Payer Id is required.",
  "errorCode": "validation_failed"
}

Validation error with a specific cause (unsupported currency):

{
  "type": "https://docs.payalo.com/errors/config_unsupported_currency",
  "title": "Validation failed",
  "status": 400,
  "detail": "Currency is not supported.",
  "errorCode": "validation_failed"
}

Business logic error (duplicate merchant reference):

{
  "type": "https://docs.payalo.com/errors/merchant_transactionid_duplicate",
  "title": "Business logic error",
  "status": 422,
  "detail": "Duplicate reference detected in merchant request.",
  "errorCode": "merchant_transactionid_duplicate"
}

Bad request (malformed JSON):

{
  "type": "https://docs.payalo.com/errors/bad_request",
  "title": "Bad request",
  "status": 400,
  "detail": "Invalid format of the request.",
  "errorCode": "bad_request"
}

Unauthorized:

{
  "type": "https://docs.payalo.com/errors/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Invalid API key",
  "errorCode": "unauthorized"
}

Async Transaction Error Codes

After a transaction is created, it is processed asynchronously. If processing fails, the transaction reaches a failed status and the callback (and GET /status response) carries an errorCode describing why.

See Async Transaction Errors for the full catalogue, grouped by category, with retryability guidance and suggested user-facing copy. Common codes you'll see: user_insufficient_funds, user_timeout, user_cancelled, provider_unavailable, provider_timeout, transaction_expired.


6. Transaction Lifecycle

Overview

The gateway follows an asynchronous, fire-and-forget model:

1. Merchant sends POST request to initiate a payment
2. Gateway validates the request synchronously
3. Gateway returns 200 OK with a gatewayReference (transaction is now "pending")
4. Gateway routes the transaction to the appropriate payment provider
5. Payment provider processes the transaction asynchronously
6. Gateway sends a callback (webhook) to the merchant's resultUrl
7. Transaction reaches terminal state: "Success" or "Failed"
sequenceDiagram
    participant M as Merchant
    participant G as Gateway
    participant P as Provider

    M->>G: POST /gateway/mmo/v2/{flow}/{direction}/{method}
    G-->>M: 200 { gatewayReference }
    G->>P: route & execute
    P-->>G: provider result
    G->>M: POST {resultUrl}
    M-->>G: 2xx (body informational only)

Transaction Status Model

Status Terminal? Description
pending No Transaction has been accepted and queued for processing. This is the initial state after successful creation.
success Yes Transaction completed successfully. finalAmount is populated; provider fee (if any) is in providerData.fee.
failed Yes Transaction was processed but ultimately failed. Check errorCode and errorMessage for the reason.

State Transitions

graph TD
    A[pending] -->|async processing| B[success - terminal]
    A[pending] -->|async processing| C[failed - terminal]

Key rules:

  • Every transaction starts in pending upon successful creation.
  • Processing happens asynchronously (routing -> validation -> execution -> post-execution).
  • A transaction always reaches one of the two terminal states: success or failed.
  • Terminal states are irreversible — once a transaction is success or failed, it will not change.

7. Callback (Webhook) Documentation

When a transaction reaches a terminal state (success or failed), the gateway POSTs the Mmo Transaction — the same shape returned by the Payment Status endpoints — to your resultUrl. Validate the X-API-KEY header on every incoming callback and respond within 15 seconds with any 2xx status code. The response body is optional and informational only — the gateway treats any 2xx as successful delivery.

For the body schema in context, delivery policy, idempotency rules, and worked examples, see the Callbacks reference.


8. Limitations & Known Constraints

Timeout Behavior

Timeout Value Description
Transaction timeout 3 days A pending transaction that has not reached a terminal state after 3 days is automatically marked failed with errorCode: transaction_expired.
Callback delivery 15 s, single attempt One attempt per transaction; no automatic retry. A per-merchant circuit breaker short-circuits further deliveries while open. See Callbacks → Delivery Policy.

Amount Limits

Amount limits (minimum and maximum) are configured per brand, per payment method, and per currency. These limits are enforced during request validation.

Contact your account manager to confirm the amount limits for your configured payment methods.

Decimal Precision

Amount precision must match the currency. For example:

  • EUR — 2 decimal places (e.g., 10.50)
  • KES — 2 decimal places (e.g., 500.00)
  • Zero-decimal currencies (e.g., JPY) — whole numbers only (e.g., 1000)

Supported Payment Methods & Currencies

Payment methods, supported currencies, and supported countries are configured per brand in the admin panel. Contact your account manager for the complete list of available payment methods and their supported currency/country combinations.


Production Readiness Checklist

Use this checklist when preparing for production:

  • [ ] Obtain your X-Api-Key from your account manager.
  • [ ] Confirm which payment methods, currencies, and countries are enabled for your brand.
  • [ ] Confirm amount limits (min/max) for each payment method.
  • [ ] Ensure your resultUrl endpoint is publicly reachable over HTTPS.
  • [ ] Implement X-API-KEY header validation on your callback endpoint.
  • [ ] Handle callbacks idempotently — deduplicate using the gatewayReference field of the callback body.
  • [ ] Respond to callbacks within 15 seconds with any 2xx status code (response body is optional — the gateway does not parse it).
  • [ ] Generate a unique merchantReference for every new transaction.
  • [ ] Never reuse a merchantReference, even if the original transaction failed.
  • [ ] Store the gatewayReference from every create response for reconciliation.
  • [ ] Implement a reconciliation job using the Payment Status endpoints as a fallback for missed callbacks.