Introduction
The CowemaPay API allows you to accept Mobile Money payments (MTN MoMo, Airtel Money) in your applications. The API follows REST conventions and returns JSON.
Base URL:
https://pay.cowema.org/api/v1
https://pay.cowema.org/api/v1 if you are working locally.
openapi.json into Bruno, Postman or Insomnia. Or use the .http file directly in VS Code (REST Client) or JetBrains.
Quick start
1. Create your account
Go to the dashboard and create an account. You will be prompted to create an organization.
2. Create an application
In the Applications menu, click New application. Give it a name and configure your webhook URLs if needed.
3. Generate your API keys
On the applications list, click the key icon next to your application, then API keys (test). A modal will display your keys:
| Key | Prefix | Visibility |
|---|---|---|
| Public key | pk_test_... | Visible at all times in the dashboard |
| Secret key | sk_test_... | Shown only once at generation |
4. Make your first call
Test with a sandbox payment:
curl -X POST https://pay.cowema.org/api/v1/payments \
-H "Authorization: Bearer sk_test_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"country": "CG",
"phone_number": "054553499",
"provider": "mtn_momo"
}'
The number 054553499 simulates a successful payment in sandbox.
API payment flow
Authentication
All API requests must include your secret key in the Authorization header.
Authorization: Bearer sk_test_votre_cle_secrete
sk_). It gives full access to your account. Use the public key (pk_) on the frontend.
| Key type | Prefix | Usage |
|---|---|---|
| Public test key | pk_test_ | Client-side identification (sandbox) |
| Secret test key | sk_test_ | API requests (sandbox) |
| Public live key | pk_live_ | Client-side identification (production) |
| Secret live key | sk_live_ | API requests (production) |
Sandbox mode
sk_test_ keys automatically activate sandbox mode. No real calls are made to providers. Use these test numbers:
Create a payment
Initiates a Mobile Money payment request. The customer will receive a notification on their phone to confirm.
Idempotency-Key header to avoid duplicates in case of retry. Learn more| Parameter | Type | Description | |
|---|---|---|---|
amount | integer | required | Amount in smallest unit (e.g.: 5000 = 5,000 XAF) |
country | string | required | ISO 2-letter country code (e.g.: CG) |
currency | string | optional | ISO currency code (e.g.: XAF) -- automatically inferred from country |
phone_number | string | required | Payer phone number |
provider | string | required | mtn_momo or airtel_money |
metadata | object | optional | Custom data (e.g.: order_id) |
curl -X POST https://pay.cowema.org/api/v1/payments \
-H "Authorization: Bearer sk_test_VOTRE_CLE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: votre-cle-unique" \
-d '{
"amount": 5000,
"country": "CG",
"phone_number": "054553499",
"provider": "mtn_momo",
"metadata": {"order_id": "ORD-123"}
}'
201 -- Payment created
{
"data": {
"id": "9f8a2b3c-...",
"type": "collection",
"provider": "mtn_momo",
"amount": 5000,
"currency": "XAF",
"country": "CG",
"phone_number": "054553499",
"status": "pending",
"environment": "test",
"metadata": { "order_id": "ORD-123" },
"created_at": "2026-03-29T18:00:00+00:00"
}
}
401 -- Not authenticated
{
"error": "Missing API key.",
"code": "missing_api_key"
}
422 -- Validation error
{
"message": "The amount field is required.",
"errors": {
"amount": ["The amount field is required."],
"provider": ["The selected provider is invalid."]
}
}
429 -- Rate limit exceeded
{
"message": "Too Many Attempts."
}
View a payment
Retrieves the details of a payment by its identifier.
curl https://pay.cowema.org/api/v1/payments/PAYMENT_UUID \ -H "Authorization: Bearer sk_test_VOTRE_CLE"
200 -- Payment found
{
"data": {
"id": "9f8a2b3c-...",
"type": "collection",
"status": "successful",
// ... memes champs que la creation
}
}
404 -- Transaction not found
{
"error": "Transaction not found.",
"code": "not_found"
}
List payments
Returns a paginated list of your payments. Filterable by status, provider and environment.
| Parameter | Type | Description | |
|---|---|---|---|
status | string | filter | pending, processing, successful, failed, cancelled |
provider | string | filter | mtn_momo or airtel_money |
environment | string | filter | live or test |
per_page | integer | filter | Number of results (max 100) |
curl "https://pay.cowema.org/api/v1/payments?status=successful&per_page=10" \ -H "Authorization: Bearer sk_test_VOTRE_CLE"
200 -- Paginated list
{
"data": [
{
"id": "9f8a2b3c-...",
"type": "collection",
"amount": 5000,
"status": "successful",
// ...
},
// ... autres transactions
],
"links": {
"first": "...?page=1",
"last": "...?page=3",
"prev": null,
"next": "...?page=2"
},
"meta": {
"current_page": 1,
"per_page": 10,
"total": 25,
"last_page": 3
}
}
422 -- Invalid filter
{
"message": "The selected status is invalid.",
"errors": {
"status": ["The selected status is invalid."]
}
}
Create a refund
Refunds a successful payment. The amount is returned to the payer's Mobile Money account.
Idempotency-Key header to avoid duplicates in case of retry. Learn more| Parameter | Type | Description | |
|---|---|---|---|
payment_id | string | required | UUID of the payment to refund |
amount | integer | required | Amount to refund |
curl -X POST https://pay.cowema.org/api/v1/refunds \
-H "Authorization: Bearer sk_test_VOTRE_CLE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: votre-cle-unique" \
-d '{
"payment_id": "PAYMENT_UUID",
"amount": 5000
}'
201 -- Refund created
{
"data": {
"id": "b7c4e1f2-...",
"type": "refund",
"status": "pending",
// ... memes champs qu'une transaction
}
}
422 -- Transaction not refundable
{
"error": "Original transaction not found or not refundable.",
"code": "not_refundable"
}
View a refund
curl https://pay.cowema.org/api/v1/refunds/REFUND_UUID \ -H "Authorization: Bearer sk_test_VOTRE_CLE"
200 -- Refund found
{
"data": {
"id": "b7c4e1f2-...",
"type": "refund",
"provider": "mtn_momo",
"amount": 5000,
"currency": "XAF",
"status": "successful",
"environment": "test",
"created_at": "2026-03-29T18:05:00+00:00"
}
}
404 -- Refund not found
{
"error": "Refund not found.",
"code": "not_found"
}
Create a Checkout session
Creates a hosted payment session. Redirect your customer to the returned URL.
Idempotency-Key header to avoid duplicates in case of retry. Learn more| Parameter | Type | Description | |
|---|---|---|---|
amount | integer | required | Amount |
country | string | required | ISO 2-letter country code (e.g.: CG) |
currency | string | optional | Currency code -- inferred from country if omitted |
success_url | string | required | Redirect URL after success |
cancel_url | string | required | Redirect URL if cancelled |
description | string | optional | Description shown to the customer |
metadata | object | optional | Custom data (e.g.: order_id) |
curl -X POST https://pay.cowema.org/api/v1/checkout/sessions \
-H "Authorization: Bearer sk_test_VOTRE_CLE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: votre-cle-unique" \
-d '{
"amount": 10000,
"country": "CG",
"description": "Abonnement Premium",
"success_url": "https://votresite.com/success",
"cancel_url": "https://votresite.com/cancel"
}'
201 -- Session created
{
"data": {
"id": "cs_abc123...",
"amount": 10000,
"currency": "XAF",
"country": "CG",
"url": "https://pay.cowema.org/checkout/cs_abc123",
"status": "open",
"expires_at": "2026-03-29T19:00:00+00:00"
}
}
422 -- Validation error
{
"message": "The success url field is required.",
"errors": {
"success_url": ["The success url field is required."]
}
}
View a checkout session
Retrieves the status of a checkout session. Use this endpoint to verify server-side if the payment was completed.
curl https://pay.cowema.org/api/v1/checkout/sessions/SESSION_UUID \ -H "Authorization: Bearer sk_test_VOTRE_CLE"
200 -- Session found
{
"data": {
"id": "cs_abc123...",
"amount": 10000,
"currency": "XAF",
"description": "Abonnement Premium",
"url": "https://pay.cowema.org/checkout/cs_abc123",
"status": "completed",
"expires_at": "2026-03-29T19:00:00+00:00",
"metadata": null,
"created_at": "2026-03-29T18:00:00+00:00"
}
}
404 -- Session not found
{
"error": "Checkout session not found.",
"code": "not_found"
}
Checkout integration flow
1. Create a session server-side
Call POST /api/v1/checkout/sessions from your backend with your secret key.
2. Redirect the customer
Send your customer to the data.url URL returned in the response.
3. The customer pays
On the CowemaPay page, they choose their provider (MTN/Airtel), enter their number, and confirm.
4. Automatic redirect
After payment, the customer is redirected to your success_url or cancel_url.
5. Verify server-side
Use GET /api/v1/checkout/sessions/{id} or webhooks to confirm the payment.
Diagram
Create a payout
Initiates a money transfer to a Mobile Money account. The amount is debited from your merchant balance.
Idempotency-Key header to avoid duplicates in case of retry. Learn more| Parameter | Type | Description | |
|---|---|---|---|
amount | integer | required | Amount in smallest unit (e.g.: 5000 = 5,000 XAF) |
currency | string | required | ISO currency code (e.g.: XAF) |
country | string | required | ISO 2-letter country code (e.g.: CG) |
provider | string | required | mtn_momo or airtel_money |
phone_number | string | required | Recipient phone number |
metadata | object | optional | Custom data (e.g.: order_id) |
Payout statuses
| Status | Description |
|---|---|
pending | Payout created, awaiting validation |
approved | Payout approved, ready to be processed |
processing | Transfer in progress with provider |
completed | Transfer completed successfully |
failed | Transfer failed |
rejected | Payout rejected |
curl -X POST https://pay.cowema.org/api/v1/payouts \
-H "Authorization: Bearer sk_test_VOTRE_CLE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: votre-cle-unique" \
-d '{
"amount": 5000,
"currency": "XAF",
"country": "CG",
"provider": "mtn_momo",
"phone_number": "066000001"
}'
201 -- Payout created
{
"data": {
"id": "d4e5f6a7-...",
"type": "payout",
"provider": "mtn_momo",
"amount": 5000,
"currency": "XAF",
"country": "CG",
"phone_number": "066000001",
"status": "pending",
"environment": "test",
"metadata": null,
"created_at": "2026-03-31T10:00:00+00:00"
}
}
422 -- Insufficient balance
{
"error": "Insufficient balance for this payout.",
"code": "insufficient_balance"
}
422 -- Validation error
{
"message": "The amount field is required.",
"errors": {
"amount": ["The amount field is required."]
}
}
List payouts
Returns a paginated list of your payouts. Filterable by status.
| Parameter | Type | Description | |
|---|---|---|---|
status | string | filter | pending, approved, processing, completed, failed, rejected |
per_page | integer | filter | Number of results (max 100) |
curl "https://pay.cowema.org/api/v1/payouts?status=completed&per_page=10" \ -H "Authorization: Bearer sk_test_VOTRE_CLE"
200 -- Paginated list
{
"data": [
{
"id": "d4e5f6a7-...",
"type": "payout",
"amount": 5000,
"status": "completed",
// ...
}
],
"links": { // ... pagination },
"meta": {
"current_page": 1,
"per_page": 10,
"total": 8,
"last_page": 1
}
}
View a payout
Retrieves the details of a payout by its identifier.
curl https://pay.cowema.org/api/v1/payouts/PAYOUT_UUID \ -H "Authorization: Bearer sk_test_VOTRE_CLE"
200 -- Payout found
{
"data": {
"id": "d4e5f6a7-...",
"type": "payout",
"provider": "mtn_momo",
"amount": 5000,
"currency": "XAF",
"country": "CG",
"phone_number": "066000001",
"status": "completed",
"environment": "test",
"created_at": "2026-03-31T10:00:00+00:00"
}
}
404 -- Payout not found
{
"error": "Payout not found.",
"code": "not_found"
}
Merchant balance
Returns the available balance of your merchant account, broken down by currency.
curl https://pay.cowema.org/api/v1/merchant/balance \ -H "Authorization: Bearer sk_test_VOTRE_CLE"
200 -- Balance retrieved
{
"data": [
{
"currency": "XAF",
"available_balance": 15885960,
"pending_balance": 0
}
]
}
Idempotency
All POST endpoints accept an Idempotency-Key header to avoid duplicates in case of network retry.
Idempotency-Key: votre-cle-unique-uuid
How it works
| Scenario | Behavior |
|---|---|
| Same key + same body | Returns the cached response (valid for 30 days) |
| Same key + different body | Returns a 409 Conflict error |
| No header | Request processed normally (no idempotency) |
curl -X POST https://pay.cowema.org/api/v1/payments \
-H "Authorization: Bearer sk_test_VOTRE_CLE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{
"amount": 5000,
"country": "CG",
"phone_number": "054553499",
"provider": "mtn_momo"
}'
409 -- Idempotency conflict
{
"error": "A request with this idempotency key already exists with a different body.",
"code": "idempotency_conflict"
}
Idempotency-Key header is optional. Use a unique UUID v4 per request to guarantee uniqueness.
Security
CowemaPay offers several configurable security mechanisms per application.
IP Whitelisting
You can restrict API calls to a list of authorized IP addresses. Configure IPs in the dashboard, on the edit page of your application.
403 -- Unauthorized IP
{
"error": "Votre adresse IP n'est pas autorisée pour cette application.",
"code": "ip_not_whitelisted"
}
Rate Limiting
By default, the API allows 100 requests per minute per application. This limit is configurable per organization.
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum number of requests per minute |
X-RateLimit-Remaining | Number of remaining requests |
Retry-After | Seconds before the next window (if limit reached) |
KYC Gate
Live mode requires a valid KYC verification for your organization. sk_live_ keys are unusable until KYC is approved.
403 -- KYC required
{
"error": "La vérification KYC est requise pour utiliser le mode live.",
"code": "kyc_verification_required"
}
Outgoing webhooks
CowemaPay sends HTTP notifications (webhooks) to your webhook_url on each status change of a payment or payout.
Signature
Each webhook includes an X-CowemaPay-Signature header containing an HMAC-SHA256 of the raw body, signed with your webhook_secret.
// Always verify the signature before processing the event $payload = file_get_contents('php://input'); $signature = $_SERVER['HTTP_X_COWEMAPAY_SIGNATURE']; $secret = 'votre_webhook_secret'; $expected = hash_hmac('sha256', $payload, $secret); if (!hash_equals($expected, $signature)) { http_response_code(401); exit('Invalid signature'); } $event = json_decode($payload, true); // Process the event...
Event types
| Event | Description |
|---|---|
payment.successful | A payment has been confirmed |
payment.failed | A payment has failed |
refund.successful | A refund has been confirmed |
refund.failed | A refund has failed |
payout.completed | A payout has been completed |
payout.failed | A payout has failed |
Retry policy
If your server does not respond with a 2xx code, CowemaPay retries up to 3 times with increasing delay:
| Attempt | Delay |
|---|---|
| 1st retry | 60 seconds |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
Payload example
// Headers Content-Type: application/json X-CowemaPay-Signature: a1b2c3d4e5... X-CowemaPay-Event: payment.successful // Body { "event": "payment.successful", "data": { "id": "9f8a2b3c-...", "type": "collection", "amount": 5000, "currency": "XAF", "country": "CG", "phone_number": "054553499", "provider": "mtn_momo", "status": "successful", "environment": "test", "metadata": { "order_id": "ORD-123" }, "created_at": "2026-03-29T18:00:00+00:00" }, "timestamp": "2026-03-29T18:00:15+00:00" }
X-CowemaPay-Signature signature before processing a webhook. Never trust the content without verification.
Provider balance
Queries the balance directly from the Mobile Money provider.
curl "https://pay.cowema.org/api/v1/balance?provider=mtn_momo" \ -H "Authorization: Bearer sk_test_VOTRE_CLE"
200 -- Balance retrieved
{
"data": {
"available": 1000000,
"currency": "XAF"
}
}
422 -- Invalid provider
{
"message": "The selected provider is invalid.",
"errors": {
"provider": ["The selected provider is invalid."]
}
}
Webhooks
CowemaPay sends webhooks to the URL configured in your application when an event occurs.
Configuration
In the dashboard, edit your application and enter the Webhook URL. A webhook_secret is generated automatically -- you will find it on the application edit page, in the Webhook secret field.
Events
| Event | Description |
|---|---|
payment.successful | A payment has been confirmed |
payment.failed | A payment has failed |
refund.successful | A refund has been confirmed |
refund.failed | A refund has failed |
Signature verification
Each webhook includes an X-CowemaPay-Signature header containing an HMAC SHA-256 of the body with your webhook_secret.
// Verify the webhook signature $payload = file_get_contents('php://input'); $signature = $_SERVER['HTTP_X_COWEMAPAY_SIGNATURE']; $secret = 'votre_webhook_secret'; $expected = hash_hmac('sha256', $payload, $secret); if (hash_equals($expected, $signature)) { // Valid webhook $event = json_decode($payload, true); // Process the event... }
Full payload
Here is an example payload sent by CowemaPay:
// Headers Content-Type: application/json X-CowemaPay-Signature: a1b2c3d4e5... X-CowemaPay-Event: payment.successful // Body { "event": "payment.successful", "data": { "id": "9f8a2b3c-...", "type": "collection", "amount": 5000, "currency": "XAF", "country": "CG", "phone_number": "054553499", "provider": "mtn_momo", "status": "successful", "environment": "test", "metadata": { "order_id": "ORD-123" }, "created_at": "2026-03-29T18:00:00+00:00" }, "timestamp": "2026-03-29T18:00:15+00:00" }
Retries
If your server does not respond with a 2xx code, CowemaPay retries up to 3 times with increasing delay: 1 minute, 5 minutes, 30 minutes.
Errors
The API uses standard HTTP codes:
| Code | Description |
|---|---|
200 | Success |
201 | Resource created |
401 | Missing or invalid API key |
403 | Access denied (unauthorized IP, KYC required) |
404 | Resource not found |
409 | Idempotency conflict |
422 | Validation error / insufficient balance |
429 | Too many requests (rate limit) |
500 | Server error |
{
"message": "The amount field is required.",
"errors": {
"amount": ["The amount field is required."]
}
}