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
In sandbox mode, you can also use https://pay.cowema.org/api/v1 if you are working locally.
OpenAPI / Swagger .http file (VS Code / JetBrains) View JSON
Import 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:

KeyPrefixVisibility
Public keypk_test_...Visible at all times in the dashboard
Secret keysk_test_...Shown only once at generation
Copy your secret key immediately. It will never be shown again. If you lose it, generate a new one (the old one will be invalidated).

4. Make your first call

Test with a sandbox payment:

curl
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

Your server CowemaPay Mobile Money Provider Your server CowemaPay Mobile Money Provider POST /api/v1/payments 201 {status: "pending"} requestToPay() Customer confirms on their phone Callback status → "successful" Webhook POST {event: "payment.successful"} 200 OK

Authentication

All API requests must include your secret key in the Authorization header.

Authentication header
Authorization: Bearer sk_test_votre_cle_secrete
Never share your secret key (sk_). It gives full access to your account. Use the public key (pk_) on the frontend.
Key typePrefixUsage
Public test keypk_test_Client-side identification (sandbox)
Secret test keysk_test_API requests (sandbox)
Public live keypk_live_Client-side identification (production)
Secret live keysk_live_API requests (production)

Sandbox mode

sk_test_ keys automatically activate sandbox mode. No real calls are made to providers. Use these test numbers:

*****0000 → Successful payment
*****0002 → Insufficient funds
*****0003 → Provider timeout
Any other → Successful payment

Create a payment

POST /api/v1/payments

Initiates a Mobile Money payment request. The customer will receive a notification on their phone to confirm.

Idempotency: Add the Idempotency-Key header to avoid duplicates in case of retry. Learn more
ParameterTypeDescription
amountintegerrequiredAmount in smallest unit (e.g.: 5000 = 5,000 XAF)
countrystringrequiredISO 2-letter country code (e.g.: CG)
currencystringoptionalISO currency code (e.g.: XAF) -- automatically inferred from country
phone_numberstringrequiredPayer phone number
providerstringrequiredmtn_momo or airtel_money
metadataobjectoptionalCustom data (e.g.: order_id)
curl
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

GET /api/v1/payments/{id}

Retrieves the details of a payment by its identifier.

curl
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

GET /api/v1/payments

Returns a paginated list of your payments. Filterable by status, provider and environment.

ParameterTypeDescription
statusstringfilterpending, processing, successful, failed, cancelled
providerstringfiltermtn_momo or airtel_money
environmentstringfilterlive or test
per_pageintegerfilterNumber of results (max 100)
curl
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

POST /api/v1/refunds

Refunds a successful payment. The amount is returned to the payer's Mobile Money account.

Idempotency: Add the Idempotency-Key header to avoid duplicates in case of retry. Learn more
ParameterTypeDescription
payment_idstringrequiredUUID of the payment to refund
amountintegerrequiredAmount to refund
curl
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

GET /api/v1/refunds/{id}
curl
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

POST /api/v1/checkout/sessions

Creates a hosted payment session. Redirect your customer to the returned URL.

Idempotency: Add the Idempotency-Key header to avoid duplicates in case of retry. Learn more
ParameterTypeDescription
amountintegerrequiredAmount
countrystringrequiredISO 2-letter country code (e.g.: CG)
currencystringoptionalCurrency code -- inferred from country if omitted
success_urlstringrequiredRedirect URL after success
cancel_urlstringrequiredRedirect URL if cancelled
descriptionstringoptionalDescription shown to the customer
metadataobjectoptionalCustom data (e.g.: order_id)
curl
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

GET /api/v1/checkout/sessions/{id}

Retrieves the status of a checkout session. Use this endpoint to verify server-side if the payment was completed.

curl
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

The hosted checkout is the simplest way to accept payments. No need to manage the form on the client side.

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

Your server CowemaPay Client Your server CowemaPay Client POST /api/v1/checkout/sessions 201 {url: "/checkout/xxx"} Redirect to checkout URL Chooses provider + enters number Phone confirmation Redirect to success_url Webhook POST {event: "payment.successful"}

Create a payout

POST /api/v1/payouts

Initiates a money transfer to a Mobile Money account. The amount is debited from your merchant balance.

Idempotency: Add the Idempotency-Key header to avoid duplicates in case of retry. Learn more
ParameterTypeDescription
amountintegerrequiredAmount in smallest unit (e.g.: 5000 = 5,000 XAF)
currencystringrequiredISO currency code (e.g.: XAF)
countrystringrequiredISO 2-letter country code (e.g.: CG)
providerstringrequiredmtn_momo or airtel_money
phone_numberstringrequiredRecipient phone number
metadataobjectoptionalCustom data (e.g.: order_id)

Payout statuses

StatusDescription
pendingPayout created, awaiting validation
approvedPayout approved, ready to be processed
processingTransfer in progress with provider
completedTransfer completed successfully
failedTransfer failed
rejectedPayout rejected
curl
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

GET /api/v1/payouts

Returns a paginated list of your payouts. Filterable by status.

ParameterTypeDescription
statusstringfilterpending, approved, processing, completed, failed, rejected
per_pageintegerfilterNumber of results (max 100)
curl
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

GET /api/v1/payouts/{id}

Retrieves the details of a payout by its identifier.

curl
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

GET /api/v1/merchant/balance

Returns the available balance of your merchant account, broken down by currency.

curl
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 header
Idempotency-Key: votre-cle-unique-uuid

How it works

ScenarioBehavior
Same key + same bodyReturns the cached response (valid for 30 days)
Same key + different bodyReturns a 409 Conflict error
No headerRequest processed normally (no idempotency)
curl -- Example with 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"
}
The 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.

HeaderDescription
X-RateLimit-LimitMaximum number of requests per minute
X-RateLimit-RemainingNumber of remaining requests
Retry-AfterSeconds 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.

PHP — Vérification de signature
// 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

EventDescription
payment.successfulA payment has been confirmed
payment.failedA payment has failed
refund.successfulA refund has been confirmed
refund.failedA refund has failed
payout.completedA payout has been completed
payout.failedA payout has failed

Retry policy

If your server does not respond with a 2xx code, CowemaPay retries up to 3 times with increasing delay:

AttemptDelay
1st retry60 seconds
2nd retry5 minutes
3rd retry30 minutes

Payload example

POST votre-webhook-url
// 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"
}
Always verify the 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.

GET /api/v1/balance?provider=mtn_momo
curl
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

EventDescription
payment.successfulA payment has been confirmed
payment.failedA payment has failed
refund.successfulA refund has been confirmed
refund.failedA 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.

PHP — Vérification
// 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:

POST votre-webhook-url
// 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:

CodeDescription
200Success
201Resource created
401Missing or invalid API key
403Access denied (unauthorized IP, KYC required)
404Resource not found
409Idempotency conflict
422Validation error / insufficient balance
429Too many requests (rate limit)
500Server error
422 error example
{
  "message": "The amount field is required.",
  "errors": {
    "amount": ["The amount field is required."]
  }
}