PayAlo Merchant API V2 Reference¶
Table of Contents¶
- Overview
- Authentication & Security
- Endpoints
- Shared Data Models
- Error Handling
- Transaction Lifecycle
- Callback (Webhook) Documentation
- Limitations & Known Constraints
- 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:
For example, to initiate a direct pay-in via M-Pesa in Kenya:
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:
| 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. |
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
payerfor pay-in endpoints andpayeefor 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.
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
pendingupon successful creation. - Processing happens asynchronously (routing -> validation -> execution -> post-execution).
- A transaction always reaches one of the two terminal states:
successorfailed. - Terminal states are irreversible — once a transaction is
successorfailed, 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-Keyfrom 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
resultUrlendpoint is publicly reachable over HTTPS. - [ ] Implement
X-API-KEYheader validation on your callback endpoint. - [ ] Handle callbacks idempotently — deduplicate using the
gatewayReferencefield of the callback body. - [ ] Respond to callbacks within 15 seconds with any
2xxstatus code (response body is optional — the gateway does not parse it). - [ ] Generate a unique
merchantReferencefor every new transaction. - [ ] Never reuse a
merchantReference, even if the original transaction failed. - [ ] Store the
gatewayReferencefrom every create response for reconciliation. - [ ] Implement a reconciliation job using the Payment Status endpoints as a fallback for missed callbacks.